{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Performance comparison"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook compares the performance of `cuDF` and `pandas`. The comparisons performed are on identical data sizes. This notebook primarily showcases the factor\n",
    "of speedups users can have when the similar `pandas` APIs are run on GPUs using `cudf`.\n",
    "\n",
    "The hardware details used to run these performance comparisons are at the end of this page.\n",
    "\n",
    "**Note**: This notebook is written to measure performance on NVIDIA GPUs with large memory. If running on hardware with lower memory, please consider lowering the `num_rows` values. Performance results may vary by data size, as well as the CPU and GPU used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cudf.__version__='24.04.00'\n"
     ]
    }
   ],
   "source": [
    "import timeit\n",
    "\n",
    "import cudf\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "print(f\"{cudf.__version__=}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "rng = np.random.default_rng(seed=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Concat, count & joins performance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "timeit_number = 30\n",
    "num_rows = 300_000_000\n",
    "sub_sample = int(num_rows / 30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>numbers</th>\n",
       "      <th>business</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>-316</td>\n",
       "      <td>Costco</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>-441</td>\n",
       "      <td>Costco</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>653</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>216</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>-165</td>\n",
       "      <td>Walmart</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999995</th>\n",
       "      <td>-395</td>\n",
       "      <td>Walmart</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999996</th>\n",
       "      <td>-653</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999997</th>\n",
       "      <td>364</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999998</th>\n",
       "      <td>159</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999999</th>\n",
       "      <td>-501</td>\n",
       "      <td>Walmart</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>300000000 rows × 2 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "           numbers business\n",
       "0             -316   Costco\n",
       "1             -441   Costco\n",
       "2              653  Buckees\n",
       "3              216  Buckees\n",
       "4             -165  Walmart\n",
       "...            ...      ...\n",
       "299999995     -395  Walmart\n",
       "299999996     -653  Buckees\n",
       "299999997      364  Buckees\n",
       "299999998      159  Buckees\n",
       "299999999     -501  Walmart\n",
       "\n",
       "[300000000 rows x 2 columns]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pdf = pd.DataFrame(\n",
    "    {\n",
    "        \"numbers\": rng.integers(-1000, 1000, num_rows, dtype=\"int64\"),\n",
    "        \"business\": rng.choice(\n",
    "            [\"McD\", \"Buckees\", \"Walmart\", \"Costco\"], size=num_rows\n",
    "        ),\n",
    "    }\n",
    ")\n",
    "pdf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>numbers</th>\n",
       "      <th>business</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>-316</td>\n",
       "      <td>Costco</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>-441</td>\n",
       "      <td>Costco</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>653</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>216</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>-165</td>\n",
       "      <td>Walmart</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999995</th>\n",
       "      <td>-395</td>\n",
       "      <td>Walmart</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999996</th>\n",
       "      <td>-653</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999997</th>\n",
       "      <td>364</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999998</th>\n",
       "      <td>159</td>\n",
       "      <td>Buckees</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999999</th>\n",
       "      <td>-501</td>\n",
       "      <td>Walmart</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>300000000 rows × 2 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "           numbers business\n",
       "0             -316   Costco\n",
       "1             -441   Costco\n",
       "2              653  Buckees\n",
       "3              216  Buckees\n",
       "4             -165  Walmart\n",
       "...            ...      ...\n",
       "299999995     -395  Walmart\n",
       "299999996     -653  Buckees\n",
       "299999997      364  Buckees\n",
       "299999998      159  Buckees\n",
       "299999999     -501  Walmart\n",
       "\n",
       "[300000000 rows x 2 columns]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gdf = cudf.from_pandas(pdf)\n",
    "gdf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def timeit_pandas_cudf(pd_obj, gd_obj, func, **kwargs):\n",
    "    \"\"\"\n",
    "    A utility function to measure execution time of an\n",
    "    API(`func`) in pandas & cudf.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    pd_obj : Pandas object\n",
    "    gd_obj : cuDF object\n",
    "    func : callable\n",
    "    \"\"\"\n",
    "    pandas_time = timeit.timeit(lambda: func(pd_obj), **kwargs)\n",
    "    cudf_time = timeit.timeit(lambda: func(gd_obj), **kwargs)\n",
    "    return pandas_time, cudf_time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pandas_value_counts, cudf_value_counts = timeit_pandas_cudf(\n",
    "    pdf, gdf, lambda df: df.value_counts(), number=timeit_number\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pdf = pdf.head(sub_sample)\n",
    "gdf = gdf.head(sub_sample)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pandas_concat = timeit.timeit(\n",
    "    lambda: pd.concat([pdf, pdf, pdf]), number=timeit_number\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "cudf_concat = timeit.timeit(\n",
    "    lambda: cudf.concat([gdf, gdf, gdf]), number=timeit_number\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pandas_groupby, cudf_groupby = timeit_pandas_cudf(\n",
    "    pdf,\n",
    "    gdf,\n",
    "    lambda df: df.groupby(\"business\").agg([\"min\", \"max\", \"mean\"]),\n",
    "    number=timeit_number,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pdf = pd.DataFrame(\n",
    "    {\n",
    "        \"numbers\": rng.integers(\n",
    "            -1000, 1000, int(sub_sample / 10), dtype=\"int64\"\n",
    "        ),\n",
    "        \"business\": rng.choice(\n",
    "            [\"McD\", \"Buckees\", \"Walmart\", \"Costco\"], size=int(sub_sample / 10)\n",
    "        ),\n",
    "    }\n",
    ")\n",
    "gdf = cudf.from_pandas(pdf)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pandas_merge, cudf_merge = timeit_pandas_cudf(\n",
    "    pdf, gdf, lambda df: df.merge(df), number=10\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>cudf speedup vs. pandas</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>value_counts</th>\n",
       "      <td>168.465151</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>concat</th>\n",
       "      <td>29.828922</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>groupby</th>\n",
       "      <td>46.671713</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>merge</th>\n",
       "      <td>45.633230</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "              cudf speedup vs. pandas\n",
       "value_counts               168.465151\n",
       "concat                      29.828922\n",
       "groupby                     46.671713\n",
       "merge                       45.633230"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "performance_df = pd.DataFrame(\n",
    "    {\n",
    "        \"cudf speedup vs. pandas\": [\n",
    "            pandas_value_counts / cudf_value_counts,\n",
    "            pandas_concat / cudf_concat,\n",
    "            pandas_groupby / cudf_groupby,\n",
    "            pandas_merge / cudf_merge,\n",
    "        ],\n",
    "    },\n",
    "    index=[\"value_counts\", \"concat\", \"groupby\", \"merge\"],\n",
    ")\n",
    "performance_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "def performance_plot(df, xlabel=None):\n",
    "    # ylim is 20% above max value\n",
    "    ylim_max = df[\"cudf speedup vs. pandas\"].max() + (\n",
    "        df[\"cudf speedup vs. pandas\"].max() / 20\n",
    "    )\n",
    "    ax = df.plot.bar(\n",
    "        color=\"#7400ff\",\n",
    "        ylim=(1, ylim_max),\n",
    "        rot=0,\n",
    "        xlabel=xlabel,\n",
    "        ylabel=\"Speedup factor\",\n",
    "    )\n",
    "    ax.bar_label(ax.containers[0], fmt=\"%.0f\")\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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/bdv34afnx9q1KgBU1NTNGrUCL/88kuZ91ddDDtERDIWGhqKefPmYdq0abh69SoiIyNha2tb5uefO3cOQUFBGD16NOLj49GpUyd89dVXUn///v1x5MgRAC+CxL179+Do6FhsO+Hh4di7dy+2b9+OpKQkbNmyBS4uLirrTJs2DX379sWlS5cQEBCAAQMGIDExEcCLM3F8fX1hbm6OkydP4tSpUzAzM0O3bt3w7NkzAMCWLVswffp0zJ07F4mJifj6668xbdo0bN68GcCLw/zvv/8+GjZsiNjYWMycORMTJkxQ6/186fr169i+fTv27duHqKgoxMXF4bPPPitxXT09PXz00UeIjIxUad+yZQvatWsHZ2dn7Nq1C0uXLsW6deuQkpKCPXv2vNbZrxMnTsT48eMRFxcHb29v+Pn5SeH0ZejasWMHrl69iunTp+M///kPtm/frrKNo0eP4saNGzh69Cg2b96MTZs2qfyHefDgwUhLS8PRo0exc+dOrF69WroWzt/3OTw8HFeuXMHmzZvx22+/YdKkSVJ/cHAw8vPzceLECSQkJGD+/PkwMzNTe3/LTJDIysoSAERWVpa2SyEiHfT06VNx9epV8fTp02J9E1C5D3VkZ2cLIyMjERERUWL/0aNHBQDx6NEjqS0uLk4AEKmpqUIIIT766CPRo0cPlef1799fKJXKUp9Tks8//1x07txZFBUVldgPQHz66acqba1btxajRo0SQgjx/fffi/r166s8Pz8/X5iYmIiDBw8KIYR46623RGRkpMo25syZI7y9vYUQQqxbt05YW1urfI5r1qwRAERcXJwQQoiNGzeq7JsQQuzevVv8/etyxowZolq1auLPP/+U2n799Vehp6cn7t27V+L+xcXFCYVCIW7fvi2EEKKwsFDUrl1brFmzRgghxOLFi8Xbb78tnj17VuLz/01qaqoAIObNmye1FRQUiDp16oj58+eX+rzg4GDRt29faTkwMFA4OzuL58+fS20ffvih6N+/vxBCiKSkJAFAnD9/XupPTEwUAMTSpUtLfZ0dO3YIa2trablJkyZi5syZZdq3V/3+lfX7m3N2dEBlH/PXFbo494BIThITE5Gfn48uXbqUaxu9e/dWafP29kZUVJRa2xk8eDDeffdd1K9fH926dcP777+Prl27FtvuP5dfniV16dIlXL9+Hebm5irr5OXl4caNG8jNzcWNGzcQFBSE4cOHS/3Pnz+XJkwnJibCw8ND5V6A/3zNsnJyckLt2rVVtlNUVISkpCTY2dkVW79p06Zwd3dHZGQkpkyZguPHjyMjIwMffvghAODDDz/EsmXLULduXXTr1g09evSAn58f9PXV+5r++/7o6+ujRYsW0ugYAKxatQrffvst7ty5g6dPn+LZs2do2rSpyjYaNWqkcj0be3t7JCQkAHjxHurr66N58+ZSf4MGDYod+jty5AjCwsJw7do1ZGdn4/nz58jLy8OTJ09QvXp1hISEYNSoUTh06BB8fHzQt29feHh4qLWv6uBhLCIimXp59dnSvLzEhPjbLRIr6ianzZo1Q2pqKubMmYOnT5+iX79++OCDD8r8/JycHDRv3hzx8fEqj+TkZHz88cfSfJCIiAiV/suXL+Ps2bNlfh09PT2V9wPQ3HsSEBAgHcqKjIxEt27dpCtyOzo6IikpCatXr4aJiQk+++wzvPPOOxr9PLZt24YJEyYgKCgIhw4dQnx8PIYMGSIdBnzJwMBAZVmhUKh16ZFbt27h/fffh4eHB3bt2oXY2FisWrUKAKTXGjZsGG7evImBAwciISEBLVq0wIoVK8q5h6Vj2CEikql69erBxMQE0dHRJfbXqlULAHDv3j2p7Z/Xm3F3d8e5c+dU2tQJD39nYWGB/v37IyIiAj/++CN27dqFhw8flrrds2fPwt3dHcCLsJSSkgIbGxu4ubmpPJRKJWxtbeHg4ICbN28W63d1dZX25Y8//kBeXl6pr1mrVi08fvwYubm5pb4nwIsz0O7evauyHT09PdSvX7/U/f/4449x+fJlxMbGYufOnQgICFDpNzExgZ+fH8LDw3Hs2DGcOXNGGlEpq7/vz/PnzxEbGyu9h6dOnULbtm3x2WefwcvLC25ubrhx44Za22/QoIG03ZeSkpJUJrnHxsaiqKgIixcvRps2bfD222+rvFcvOTo64tNPP8VPP/2E8ePHIyIiQq1a1MGwQ0QkU8bGxpg8eTImTZqE7777Djdu3MDZs2exYcMGAICbmxscHR0xc+ZMpKSk4MCBA1i8eLHKNkJCQhAVFYVFixYhJSUFK1euVPsQFvDi7JytW7fi2rVrSE5Oxo4dO2BnZ6dy+GPHjh349ttvkZycjBkzZuD8+fMYPXo0gBejIjVr1oS/vz9OnjyJ1NRUHDt2DCEhIfjzzz8BALNmzUJYWBjCw8ORnJyMhIQEbNy4EUuWLAHwImwoFAoMHz4cV69exS+//IJFixap1Nm6dWtUr14d//nPf3Djxg1ERkaqTM79+3sbGBiIS5cu4eTJkwgJCUG/fv1KPIT1kouLC9q2bYugoCAUFhaiZ8+eUt+mTZuwYcMGXL58GTdv3sQPP/wAExMT6YKvoaGhGDRo0L++z6tWrcLu3btx7do1BAcH49GjRxg6dCiAF+E3JiYGBw8eRHJyMqZNm4YLFy786zb/7uVhyJEjR+LcuXOIjY3FsGHDVEYR3dzcUFBQgBUrVuDmzZv4/vvvi119f8yYMTh48CBSU1Nx8eJFHD16VAplFYFhh4hIxqZNm4bx48dj+vTpcHd3R//+/aUzZwwMDKQA4uHhgfnz56ucaQUAbdq0QUREBJYvXw5PT08cOnQIU6dOVbsOc3NzLFiwAC1atEDLli1x69Yt/PLLLypXa581axa2bdsGDw8PfPfdd9i6dat0e5vq1avjxIkTcHJyQp8+feDu7o6goCDk5eXBwsICwItDI9988w02btyIJk2aoEOHDti0aZM0smNmZoZ9+/YhISEBXl5e+PLLLzF//nyVOq2srPDDDz/gl19+QZMmTbB161bMnDmz2P64ubmhT58+6NGjB7p27QoPD49ip7mXJCAgAJcuXULv3r1VAoKlpSUiIiLQrl07eHh44MiRI9i3b590mOvevXu4c+fOv25/3rx5mDdvHjw9PfH7779j7969qFmzJgBg5MiR6NOnD/r374/WrVvjwYMHpZ5B9iobN26Eg4MDOnTogD59+mDEiBGwsbGR+j09PbFkyRLMnz8fjRs3xpYtW1QuMwC8OA0+ODgY7u7u6NatG95+++0yvX+vSyH+eXDyDZSdnQ2lUomsrCzpl6YycYIykW7Ly8tDamoqXF1dVSa3kuYoFArs3r0bvXr10nYp/2rmzJnYs2ePTt1i4tatW3B1dUVcXFyxCcdV3at+/8r6/c2RHSIiIpI1hh0iIiKSNV5nh4iItK4qzaiYOXNmifN4tMnFxaVKvYeVjSM7REREJGsMO0RERCRrDDtERGXEwwRElU8Tv3cMO0RE/+Ll5fOfPHmi5UqI3jwvf+/+eRsLdWh1gvKJEyewcOFCxMbG4t69eyVeYyExMRGTJ0/G8ePH8fz5czRs2BC7du2Ck5MTgBfn348fPx7btm1Dfn4+fH19sXr1atja2mphj4hIjqpVqwZLS0vpYnzVq1eHQvGGXiCLqJIIIfDkyRNkZGTA0tJS5eak6tJq2MnNzYWnpyeGDh2KPn36FOu/ceMG2rdvj6CgIMyaNQsWFha4cuWKykWFxo4diwMHDmDHjh1QKpUYPXo0+vTpg1OnTlXmrhCRzL28DcDLwENElcPS0vKVt+EoC525gnJJV88cMGAADAwM8P3335f4nKysLNSqVQuRkZHS3XOvXbsGd3d3nDlzBm3atCnTa/MKytrBKyhTVVRYWFhhdwYnIlUGBgavHNEp6/e3zl5np6ioCAcOHMCkSZPg6+uLuLg4uLq6IjQ0VApEsbGxKCgogI+Pj/S8Bg0awMnJ6ZVhJz8/H/n5+dJydnZ2he4LEclHtWrVyjWcTkSVT2cnKGdkZCAnJwfz5s1Dt27dcOjQIfTu3Rt9+vTB8ePHAQDp6ekwNDRUuWsuANja2iI9Pb3UbYeFhUGpVEoPR0fHitwVIiIi0iKdDTtFRUUAAH9/f4wdOxZNmzbFlClT8P777xe7Vby6QkNDkZWVJT3S0tI0UTIRERHpIJ09jFWzZk3o6+ujYcOGKu3u7u74/fffAbyYMPjs2TNkZmaqjO7cv3//lZOZjIyMYGRkVCF1ExERkW7R2ZEdQ0NDtGzZEklJSSrtycnJcHZ2BgA0b94cBgYGiI6OlvqTkpJw584deHt7V2q9REREpJu0OrKTk5OD69evS8upqamIj4+HlZUVnJycMHHiRPTv3x/vvPMOOnXqhKioKOzbtw/Hjh0DACiVSgQFBWHcuHGwsrKChYUFPv/8c3h7e5f5TCwiIiKSN62GnZiYGHTq1ElaHjduHAAgMDAQmzZtQu/evbF27VqEhYUhJCQE9evXx65du9C+fXvpOUuXLoWenh769u2rclFBIiIiIkCHrrOjTbzOjnbwOjtERFQeZf3+1tk5O0RERESawLBDREREssawQ0RERLLGsENERESyxrBDREREssawQ0RERLLGsENERESyxrBDREREssawQ0RERLLGsENERESyxrBDREREssawQ0RERLLGsENERESyxrBDREREssawQ0RERLLGsENERESyxrBDREREssawQ0RERLLGsENERESyxrBDREREssawQ0RERLLGsENERESyxrBDREREssaw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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "performance_plot(performance_df, xlabel=\"Operation\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import gc\n",
    "\n",
    "# Cleaning up used memory for later benchmarks\n",
    "del pdf\n",
    "del gdf\n",
    "\n",
    "_ = gc.collect()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Strings Performance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "timeit_number = 20\n",
    "num_rows = 300_000_000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pd_series = pd.Series(\n",
    "    rng.choice(\n",
    "        [\"123\", \"56.234\", \"Walmart\", \"Costco\", \"rapids ai\"], size=num_rows\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "gd_series = cudf.from_pandas(pd_series)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pandas_upper, cudf_upper = timeit_pandas_cudf(\n",
    "    pd_series, gd_series, lambda s: s.str.upper(), number=timeit_number\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pandas_contains, cudf_contains = timeit_pandas_cudf(\n",
    "    pd_series,\n",
    "    gd_series,\n",
    "    lambda s: s.str.contains(r\"[0-9][a-z]\"),\n",
    "    number=timeit_number,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "pandas_isalpha, cudf_isalpha = timeit_pandas_cudf(\n",
    "    pd_series, gd_series, lambda s: s.str.isalpha(), number=timeit_number\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>cudf speedup vs. pandas</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>upper</th>\n",
       "      <td>376.502445</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>contains</th>\n",
       "      <td>405.030084</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>isalpha</th>\n",
       "      <td>1974.166058</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "          cudf speedup vs. pandas\n",
       "upper                  376.502445\n",
       "contains               405.030084\n",
       "isalpha               1974.166058"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "performance_df = pd.DataFrame(\n",
    "    {\n",
    "        \"cudf speedup vs. pandas\": [\n",
    "            pandas_upper / cudf_upper,\n",
    "            pandas_contains / cudf_contains,\n",
    "            pandas_isalpha / cudf_isalpha,\n",
    "        ],\n",
    "    },\n",
    "    index=[\"upper\", \"contains\", \"isalpha\"],\n",
    ")\n",
    "performance_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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6+sjLi4O4eHhyMvLg5OTE8aNG6cSdszMzLB7924EBATAw8MDtWrVwpQpU3jLPREREYlkgiAIFV1EZadUKmFmZoacnByYmppWdDlERPQWJsgquoLqI7KSJwh1vr+rxBgiIiIiorLEQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJJXoYEoPj4efn5+sLW1hUwmw5YtW1S2Dxs2DDKZTOXl4+Oj0ubu3bsYOHAgTE1NYW5uDn9/f+Tm5qq0SUpKwvvvvw8DAwPY2dlh9uzZZX1oREREVIVUaCB68OABmjZtisWLF7+0jY+PD9LT08XXmjVrVLYPHDgQ586dw549e7B9+3bEx8dj1KhR4nalUgkvLy84ODggMTERkZGRCA8Px9KlS8vsuIiIiKhq0anInfv6+sLX1/eVbfT19SGXy0vcduHCBezcuRMnTpxAixYtAACLFi1C9+7dMWfOHNja2iI2Nhb5+flYsWIF9PT00LhxYygUCkRFRakEp+fl5eUhLy9PXFYqlW94hERERFQVVPoxRAcOHICVlRWcnZ0xZswY3LlzR9yWkJAAc3NzMQwBQNeuXaGlpYVjx46Jbdq3bw89PT2xjbe3N1JSUpCdnV3iPiMiImBmZia+7OzsyujoiIiIqDKo1IHIx8cHv/76K/bt24dZs2bh4MGD8PX1RUFBAQAgIyMDVlZWKu/R0dGBhYUFMjIyxDbW1tYqbYqWi9q8KCwsDDk5OeLrxo0bmj40IiIiqkQq9JLZ6/Tr10/82dXVFW5ubqhXrx4OHDiALl26lNl+9fX1oa+vX2b9ExERUeVSqc8Qvahu3bqoVasWLl++DACQy+XIyspSafP06VPcvXtXHHckl8uRmZmp0qZo+WVjk4iIiEhaqlQgunnzJu7cuQMbGxsAgKenJ+7du4fExESxzV9//YXCwkK0atVKbBMfH48nT56Ibfbs2QNnZ2fUrFmzfA+AiIiIKqUKDUS5ublQKBRQKBQAgNTUVCgUCqSlpSE3NxcTJkzA0aNHce3aNezbtw8ffvgh6tevD29vbwCAi4sLfHx8MHLkSBw/fhyHDx9GYGAg+vXrB1tbWwDAgAEDoKenB39/f5w7dw5r167FggULEBISUlGHTURERJVMhQaikydPwt3dHe7u7gCAkJAQuLu7Y8qUKdDW1kZSUhJ69uyJd999F/7+/vDw8MChQ4dUxvfExsaiYcOG6NKlC7p374527dqpzDFkZmaG3bt3IzU1FR4eHhg/fjymTJny0lvuiYiISHpkgiAIFV1EZadUKmFmZoacnByYmppWdDlERPQWJsgquoLqI7KSJwh1vr+r1BgiIiIiorLAQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSp1Ygevr0KaZPn46bN2+WVT1ERERE5U6tQKSjo4PIyEg8ffq0rOohIiIiKndqXzLr3LkzDh48WBa1EBEREVUIHXXf4Ovri6+//hrJycnw8PBAjRo1VLb37NlTY8URERERlQeZIAiCOm/Q0nr5SSWZTIaCgoK3LqqyUSqVMDMzQ05ODkxNTSu6HCIiegsTZBVdQfURqVaCKH/qfH+rfYaosLDwjQsjIiIiqox42z0RERFJ3hsFooMHD8LPzw/169dH/fr10bNnTxw6dEjTtRERERGVC7UD0apVq9C1a1cYGRkhKCgIQUFBMDQ0RJcuXbB69eqyqJGIiIioTKk9qNrFxQWjRo3CuHHjVNZHRUVh2bJluHDhgkYLrAw4qJqIqPrgoGrNqU6DqtU+Q3T16lX4+fkVW9+zZ0+kpqaq1Vd8fDz8/Pxga2sLmUyGLVu2iNuePHmC0NBQuLq6okaNGrC1tcWQIUNw69YtlT4cHR0hk8lUXjNnzlRpk5SUhPfffx8GBgaws7PD7Nmz1aqTiIiIqje1A5GdnR327dtXbP3evXthZ2enVl8PHjxA06ZNsXjx4mLbHj58iFOnTmHy5Mk4deoUNm3ahJSUlBLnOZo+fTrS09PF1xdffCFuUyqV8PLygoODAxITExEZGYnw8HAsXbpUrVqJiIio+lL7tvvx48cjKCgICoUCbdq0AQAcPnwYMTExWLBggVp9+fr6wtfXt8RtZmZm2LNnj8q6H374Ae+99x7S0tJgb28vrjcxMYFcLi+xn9jYWOTn52PFihXQ09ND48aNoVAoEBUVhVGjRqlVLxEREVVPap8hGjNmDOLi4pCcnIzg4GAEBwfj7NmzWLt2LT7//POyqFGUk5MDmUwGc3NzlfUzZ86EpaUl3N3diz1rLSEhAe3bt4eenp64ztvbGykpKcjOzi5xP3l5eVAqlSovIiIiqr7UPkMEAB999BE++ugjTdfySo8fP0ZoaCj69++vMjAqKCgIzZs3h4WFBY4cOYKwsDCkp6cjKioKAJCRkQEnJyeVvqytrcVtNWvWLLaviIgITJs2rQyPhoiIiCoTtc8Q1a1bF3fu3Cm2/t69e6hbt65GinrRkydP0KdPHwiCgOjoaJVtISEh6NixI9zc3DB69GjMnTsXixYtQl5e3hvvLywsDDk5OeLrxo0bb3sIREREVImpfYbo2rVrJT6vLC8vD//++69GinpeURi6fv06/vrrr9feNteqVSs8ffoU165dg7OzM+RyOTIzM1XaFC2/bNyRvr4+9PX1NXMAREREVOmVOhBt3bpV/HnXrl0wMzMTlwsKCrBv3z44OjpqtLiiMHTp0iXs378flpaWr32PQqGAlpYWrKysAACenp745ptv8OTJE+jq6gIA9uzZA2dn5xIvlxEREZH0lDoQ9erVC8CzJ9oPHTpUZZuuri4cHR0xd+5ctXaem5uLy5cvi8upqalQKBSwsLCAjY0NPvnkE5w6dQrbt29HQUEBMjIyAAAWFhbQ09NDQkICjh07hk6dOsHExAQJCQkYN24cBg0aJIadAQMGYNq0afD390doaCjOnj2LBQsWYN68eWrVSkRERNWX2jNVOzk54cSJE6hVq9Zb7/zAgQPo1KlTsfVDhw5FeHh4scHQRfbv34+OHTvi1KlTGDt2LP755x/k5eXByckJgwcPRkhIiMolr6SkJAQEBIh1f/HFFwgNDS11nZypmoio+uBM1ZpTnWaqVjsQSREDERFR9cFApDnVKRCpfZdZUFAQFi5cWGz9Dz/8gODgYHW7IyIiIqpwageijRs3om3btsXWt2nTBhs2bNBIUURERETlSe1AdOfOHZU7zIqYmpriv//+00hRREREROVJ7UBUv3597Ny5s9j6P//8s8wmZiQiIiIqS2pPzBgSEoLAwEDcvn0bnTt3BgDs27cPc+fOxfz58zVdHxEREVGZUzsQDR8+HHl5efjuu+8wY8YMAICjoyOio6MxZMgQjRdIREREVNbe6rb727dvw9DQEMbGxpqsqdLhbfdERNUHb7vXnOp02/0bPe2+SO3atd/m7URERESVwhsFog0bNmDdunVIS0tDfn6+yrZTp05ppDAiIiKi8qL2XWYLFy7EZ599Bmtra5w+fRrvvfceLC0tcfXqVfj6+pZFjURERERlSu1AtGTJEixduhSLFi2Cnp4eJk6ciD179iAoKAg5OTllUSMRERFRmVI7EKWlpaFNmzYAAENDQ9y/fx8AMHjwYKxZs0az1RERERGVA7UDkVwux927dwEA9vb2OHr0KAAgNTUVfE4sERERVUVqB6LOnTtj69atAIDPPvsM48aNQ7du3dC3b1989NFHGi+QiIiIqKypfZfZ0qVLUVhYCAAICAiApaUljhw5gp49e+Lzzz/XeIFEREREZa1UZ4g+/vhjKJVKAMCqVatQUFAgbuvXrx8WLlyIL774Anp6emVTJREREVEZKlUg2r59Ox48eADg2WUy3k1GRERE1UmpLpk1bNgQYWFh6NSpEwRBwLp16146BTafZ0ZERERVTameZXbkyBGEhITgypUruHv3LkxMTCCTFX8YjEwmE+9Aq074LDMiouqDzzLTHMk9y6xNmzbi7fVaWlq4ePEirKys3r5SIiIiokpA7dvuU1NT+VBXIiIiqlbUvu3ewcGhLOogIiIiqjBqnyEiIiIiqm4YiIiIiEjyGIiIiIhI8tQeQ1QkKysLKSkpAABnZ2fedUZERERVltpniO7fv4/BgwfjnXfeQYcOHdChQwe88847GDRoEGewJiIioipJ7UA0YsQIHDt2DNu3b8e9e/dw7949bN++HSdPnuTDXYmIiKhKUvuS2fbt27Fr1y60a9dOXOft7Y1ly5bBx8dHo8URERERlQe1zxBZWlrCzMys2HozMzPUrFlTI0URERERlSe1A9GkSZMQEhKCjIwMcV1GRgYmTJiAyZMna7Q4IiIiovKg9iWz6OhoXL58Gfb29rC3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hIaGYuLEiYiMjNRgpURUWTAQ0VurU6cOZs6cicTERJw8eRKdO3fGhx9+iHPnzsHOzg7p6ekqr2nTpsHY2Ficibxv377F2nh7e6NDhw6wsrKq4KOjqigxMRFZWVlo3rw5dHR0oKOjg4MHD2LhwoXQ0dGBtbX1ayd2LUmrVq1w8+ZN5OXllfEREFF54yUzemt+fn4qy9999x2io6Nx9OhRNG7cuNgXzObNm9GnTx8YGxsDAAwNDVUexXL79m389ddfWL58edkXT9VSly5dkJycrLLus88+Q8OGDREaGgo7OztxYtfevXsDKD6xa0kUCgVq1qzJecyIqiEGItKogoICrF+/Hg8ePCjxiyUxMREKhQKLFy9+aR+//vorjIyM8Mknn5RlqVSNmZiYoEmTJirratSoAUtLS3H96yZ23bZtGzIzM9G6dWsYGBhgz549+P777/HVV1+V+/EQUdljICKNSE5OhqenJx4/fgxjY2Ns3ry5xMejLF++HC4uLmjTps1L+1q+fDkGDBhQpg/wJZo3bx60tLTQu3dv5OXlwdvbG0uWLBG36+rqYvHixRg3bhwEQUD9+vURFRWFkSNHVmDVRFRWODFjKXBixtfLz89HWloacnJysGHDBvz88884ePCgSih69OgRbGxsMHnyZIwfP77EfhISEtCmTRucPHkSHh4e5VU+EUkIJ2bUnOo0MSPPEJFG6OnpoX79+gAADw8PnDhxAgsWLMBPP/0kttmwYQMePnyIIUOGvLSfn3/+Gc2aNWMYIiKicsVARGWisLCw2J04y5cvR8+ePcVn0b0oNzcX69atQ0RERHmUSOWE/xrXnMr+r3GiqoyBiN5aWFgYfH19YW9vj/v372P16tU4cOAAdu3aJba5fPky4uPjsWPHjpf2s3btWjx9+hSDBg0qj7KJiIhEDET01rKysjBkyBCkp6fDzMwMbm5u2LVrF7p16ya2WbFiBerUqQMvL6+X9rN8+XJ8/PHHxWYPJiIiKmscVF0KHFRN9OZ4yUxzeMlMM/iZ1JzK/pnk0+6JiIiI1MBLZtUM/+WjGZX9Xz1ERKRZPENEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLHQERERESSx0BEREREksdARERERJLH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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "performance_plot(performance_df, xlabel=\"String method\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## User-defined function (UDF) performance (with JIT overhead)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first UDF runs include JIT compilation overhead, due to which the performance of first run and average of next few runs are compared separately."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_rows = 10_000_000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>age</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>28</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>29</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>81</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>69</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999995</th>\n",
       "      <td>38</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999996</th>\n",
       "      <td>95</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999997</th>\n",
       "      <td>19</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999998</th>\n",
       "      <td>67</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999999</th>\n",
       "      <td>29</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>10000000 rows × 1 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "         age\n",
       "0          6\n",
       "1         28\n",
       "2         29\n",
       "3         81\n",
       "4         69\n",
       "...      ...\n",
       "9999995   38\n",
       "9999996   95\n",
       "9999997   19\n",
       "9999998   67\n",
       "9999999   29\n",
       "\n",
       "[10000000 rows x 1 columns]"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pdf_age = pd.DataFrame(\n",
    "    {\n",
    "        \"age\": rng.integers(0, 100, num_rows),\n",
    "    }\n",
    ")\n",
    "pdf_age"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>age</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>28</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>29</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>81</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>69</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999995</th>\n",
       "      <td>38</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999996</th>\n",
       "      <td>95</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999997</th>\n",
       "      <td>19</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999998</th>\n",
       "      <td>67</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9999999</th>\n",
       "      <td>29</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>10000000 rows × 1 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "         age\n",
       "0          6\n",
       "1         28\n",
       "2         29\n",
       "3         81\n",
       "4         69\n",
       "...      ...\n",
       "9999995   38\n",
       "9999996   95\n",
       "9999997   19\n",
       "9999998   67\n",
       "9999999   29\n",
       "\n",
       "[10000000 rows x 1 columns]"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gdf_age = cudf.from_pandas(pdf_age)\n",
    "gdf_age"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def age_udf(row):\n",
    "    if row[\"age\"] < 18:\n",
    "        return 0\n",
    "    elif 18 <= row[\"age\"] < 20:\n",
    "        return 1\n",
    "    elif 20 <= row[\"age\"] < 30:\n",
    "        return 2\n",
    "    elif 30 <= row[\"age\"] < 40:\n",
    "        return 3\n",
    "    elif 40 <= row[\"age\"] < 50:\n",
    "        return 4\n",
    "    elif 50 <= row[\"age\"] < 60:\n",
    "        return 5\n",
    "    elif 60 <= row[\"age\"] < 70:\n",
    "        return 6\n",
    "    else:\n",
    "        return 7"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "pandas_int_udf, cudf_int_udf = timeit_pandas_cudf(\n",
    "    pdf_age, gdf_age, lambda df: df.apply(age_udf, axis=1), number=1\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def str_isupper_udf(row):\n",
    "    if row.isupper():\n",
    "        return 0\n",
    "    else:\n",
    "        return 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0                  ABC\n",
       "1          hello world\n",
       "2          hello world\n",
       "3                   AI\n",
       "4                   AI\n",
       "              ...     \n",
       "9999995    hello world\n",
       "9999996            abc\n",
       "9999997            ABC\n",
       "9999998            ABC\n",
       "9999999             AI\n",
       "Name: strings, Length: 10000000, dtype: object"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pd_series = pd.Series(\n",
    "    rng.choice([\"ABC\", \"abc\", \"hello world\", \"AI\"], size=num_rows),\n",
    "    name=\"strings\",\n",
    ")\n",
    "pd_series"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0                  ABC\n",
       "1          hello world\n",
       "2          hello world\n",
       "3                   AI\n",
       "4                   AI\n",
       "              ...     \n",
       "9999995    hello world\n",
       "9999996            abc\n",
       "9999997            ABC\n",
       "9999998            ABC\n",
       "9999999             AI\n",
       "Name: strings, Length: 10000000, dtype: object"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gd_series = cudf.from_pandas(pd_series)\n",
    "gd_series"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "pandas_str_udf, cudf_str_udf = timeit_pandas_cudf(\n",
    "    pd_series, gd_series, lambda s: s.apply(str_isupper_udf), number=1\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>cudf speedup vs. pandas</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Numeric</th>\n",
       "      <td>20.335476</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>String</th>\n",
       "      <td>8.280955</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "         cudf speedup vs. pandas\n",
       "Numeric                20.335476\n",
       "String                  8.280955"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "performance_df = pd.DataFrame(\n",
    "    {\n",
    "        \"cudf speedup vs. pandas\": [\n",
    "            pandas_int_udf / cudf_int_udf,\n",
    "            pandas_str_udf / cudf_str_udf,\n",
    "        ]\n",
    "    },\n",
    "    index=[\"Numeric\", \"String\"],\n",
    ")\n",
    "performance_df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Below is the plot showing performance speedup in case of Numeric UDFs & String UDFs on their first runs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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9thjqlOnjvbt26cJEybo+eeft+v7vOHo0aNauXKlPvnkE23YsEEHDhzQ0KFD893Wzc1NvXv31rJly2yWL126VA888ICqVaum1atX6/XXX9dbb72lI0eOaN26ddZgaY9Ro0bpueee04EDB9SiRQt16tTJGlhvBLFVq1bphx9+0Lhx4/Tiiy9q5cqVNm1s3rxZx44d0+bNm7V48WLFxcUpLi7Our5///46efKkNm/erA8//FDz58/XuXPn8hzz7Nmz9f3332vx4sX68ssvNXr0aOv66OhoZWZmatu2bTp48KCmTp0qb29vu4/XHtwCg43i7sAJ53KFDrQoPpcuXdIbb7yhuXPnql+/fpKku+++W61atSp0G2+88YaioqKsP161atXSjh07tGHDBknXR+qtWLGiJKly5co3HUYiOTlZ99xzj1q1aiWTyaRq1arl2aZnz54aNGiQJGnixInauHGj5syZo/nz52vFihXKzc3VO++8Y70as2jRIvn5+WnLli3q0KGDxo8frxkzZqh79+6SpNDQUP3www9666231K9fPy1btky5ubl699135enpqbp16+rUqVMaMmRIob+PG65evar3339fd911lyRpzpw5evTRRzVjxox8v4M+ffpoxowZSk5OVkhIiHJzc7V8+XK99NJL1u8nICBAERERKl26tEJCQtSsWTO76xo2bJh69Ogh6XpfyA0bNujdd9/V6NGjVbp0ab388svWbUNDQ7Vz506tXLlSvXr1si4vX7685s6dq1KlSiksLEyPPvqo4uPjNXjwYP3000/6/PPPtWfPHjVt2lSS9O677yo8PNymjj92hq5evbomTZqkZ5991npFLjk5WT169LC5eljUuAIEAAZx+PBhZWZmqn379rfVRvPmzW2W3cpQEf3791dCQoJq166t4cOH63//+1+ebf7cbosWLaxXgL799lsdPXpUPj4+8vb2lre3typUqKCrV6/q2LFjunz5so4dO6aBAwda13t7e2vSpEk6duyY9Vjq168vT0/P2zoWSQoJCbGGnxvt5ObmKjExMd/tGzZsqPDwcOtVoK1bt+rcuXPq2bOnpOvh7/fff1eNGjU0ePBgrV27VteuXbO7rj8ej7u7u5o0aWL9DiVp3rx5aty4sSpXrixvb2+9/fbbSk5Otmmjbt26Nq+cCAwMtF7hOXz4sNzd3dW4cWPr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/syZMzbTe++9J5PJpB49ehTYbt26dW32++qrr4qifAAAUEK5O/PDO3bsqI4dO950fUBAgM38Rx99pHbt2qlGjRoFtuvu7p5nXwAAgBtKTB+gs2fP6tNPP9XAgQP/ctsjR44oKChINWrUUJ8+fZScnFzg9pmZmUpPT7eZAADAnavEBKDFixfLx8dH3bt3L3C75s2bKy4uThs2bNCCBQuUlJSkBx98UJcuXbrpPrGxsTKbzdYpODjY0eUDAAAXUmIC0Hvvvac+ffrI09OzwO06duyonj17qn79+oqMjNRnn32m1NRUrVy58qb7xMTEKC0tzTqdPHnS0eUDAAAX4tQ+QIW1fft2JSYmasWKFXbv6+fnp1q1auno0aM33cbDw0MeHh63UyIAAChBSsQVoHfffVeNGzdWgwYN7N43IyNDx44dU2BgYBFUBgAASiKnBqCMjAwlJCQoISFBkpSUlKSEhASbTsvp6elatWqVBg0alG8b7du319y5c63zzz//vLZu3arjx49rx44d6tatm0qVKqXevXsX6bEAAICSw6m3wPbu3at27dpZ50eOHClJ6tevn+Li4iRJy5cvl8ViuWmAOXbsmH799Vfr/KlTp9S7d29duHBBlStXVqtWrbRr1y5Vrly56A4EAACUKCaLxWJxdhGuJj09XWazWWlpafL19XV2OcVqlMnZFaA4TefsB3AHsef3u0T0AQIAAHAkAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcAhAAADAcpwagbdu2qVOnTgoKCpLJZNK6dets1vfv318mk8lmioqK+st2582bp+rVq8vT01PNmzfXnj17iugIAABASeTUAHT58mU1aNBA8+bNu+k2UVFROnPmjHX64IMPCmxzxYoVGjlypMaPH6/9+/erQYMGioyM1Llz5xxdPgAAKKHcnfnhHTt2VMeOHQvcxsPDQwEBAYVuc+bMmRo8eLAG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OVMzZ87Ujh079OKLL2r06NFFUSMAAHbJycnR2LFjFRoaKi8vL919992aOHGibuP1l7jD2B2Azpw5o759++ZZ/ve//11nzpxxSFEAANyOqVOnasGCBZo7d64OHz6sqVOnatq0aZozZ46zS4OLsLsPUNu2bbV9+3bVrFnTZvlXX32lBx980GGFAQBwq3bs2KEuXbro0UcflXT9bQUffPCB9uzZ4+TK4CrsDkCdO3fWmDFjtG/fPt1///2SpF27dmnVqlV6+eWX9fHHH9tsCwBAcWvZsqXefvtt/fTTT6pVq5a+/fZbffXVV5o5c6azS4OLMFnsvCHq5la4u2YmkynfV2aUBOnp6TKbzUpLS5Ovr6+zyylWo0zOrgDFaTrdIXCHys3N1Ysvvqhp06apVKlSysnJ0eTJkxUTE+Ps0lCE7Pn9tvsKUG5u7i0XBgBAcVi5cqWWLl2qZcuWqW7dukpISNCIESMUFBSkfv36Obs8uIBbGgcIAABXNmrUKL3wwgt64oknJEn16tXTiRMnFBsbSwCCJDueAtu5c6fWr19vs+z9999XaGioqlSpoqefflqZmZkOLxAAAHtduXIlT5eNUqVKcRcDVoUOQK+88oq+//576/zBgwc1cOBARURE6IUXXtAnn3yi2NjYIikSAAB7dOrUSZMnT9ann36q48ePa+3atZo5c6a6devm7NLgIgp9CywhIUETJ060zi9fvlzNmzfXwoULJUnBwcEaP368JkyY4PAiAQCwx5w5czR27FgNHTpU586dU1BQkJ555hmNGzfO2aXBRRQ6AF28eFH+/v7W+a1bt6pjx47W+aZNm+rkyZOOrQ4AgFvg4+OjWbNmadasWc4uBS6q0LfA/P39lZSUJEnKysrS/v37reMASdKlS5dUunRpx1cIAADgYIW+AvTII4/ohRde0NSpU7Vu3TqVLVvWZuTn7777TnfffXeRFAkAuH2M82UsjPNVsEIHoIkTJ6p79+5q06aNvL29tXjxYpUpU8a6/r333lOHDh2KpEgAAABHKnQAqlSpkrZt26a0tDR5e3urVKlSNutXrVolb29vhxcIAADgaHYPhGg2m/NdXqFChdsuBgAAoDgUuhM0AADAnYIABAAADIcABAAADIcABAAADIcABAAADIcABAAADIcABAAADMflA1D16tVlMpnyTNHR0fluHxcXl2dbT0/P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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "performance_plot(performance_df, xlabel=\"UDF Kind\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## User-defined function (UDF) performance (without JIT overhead)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "timeit_number = 10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pandas_int_udf, cudf_int_udf = timeit_pandas_cudf(\n",
    "    pdf_age,\n",
    "    gdf_age,\n",
    "    lambda df: df.apply(age_udf, axis=1),\n",
    "    number=timeit_number,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "pandas_str_udf, cudf_str_udf = timeit_pandas_cudf(\n",
    "    pd_series,\n",
    "    gd_series,\n",
    "    lambda s: s.apply(str_isupper_udf),\n",
    "    number=timeit_number,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>cudf speedup vs. pandas</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Numeric</th>\n",
       "      <td>21377.625003</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>String</th>\n",
       "      <td>37.422872</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "         cudf speedup vs. pandas\n",
       "Numeric             21377.625003\n",
       "String                 37.422872"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "performance_df = pd.DataFrame(\n",
    "    {\n",
    "        \"cudf speedup vs. pandas\": [\n",
    "            pandas_int_udf / cudf_int_udf,\n",
    "            pandas_str_udf / cudf_str_udf,\n",
    "        ]\n",
    "    },\n",
    "    index=[\"Numeric\", \"String\"],\n",
    ")\n",
    "performance_df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Below is the plot showing performance speedup in case of Numeric UDFs & String UDFs on their consequent runs. In this case the speedup is massive because of no JIT overhead present."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAk0AAAGwCAYAAAC0HlECAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAABPo0lEQVR4nO3de3zP9f//8dt72IGdHHfIMCmGIRJTichIaqWDQw41hEko1sphUU3kmPBxyPRBDoUPKlrLUObQWM4TphHDt7K3Qzaz/f7ostevd0OvN2Pvzf16ubwvl71ez+f7+X683rW9716v5/v5suTm5uYiIiIiItflVNgFiIiIiBQFCk0iIiIiJig0iYiIiJig0CQiIiJigkKTiIiIiAkKTSIiIiImKDSJiIiImFCysAsoLnJycjhx4gQeHh5YLJbCLkdERERMyM3N5dy5c/j7++PkdP1zSQpNBeTEiRMEBAQUdhkiIiJyA44dO0blypWv20ehqYB4eHgAf73pnp6ehVyNiIiImGG1WgkICDA+x69HoamA5F2S8/T0VGgSEREpYsxMrdFEcBERERETFJpERERETFBoEhERETFBc5rkjhATE8Py5cs5cOAAbm5uNGvWjA8++ICaNWsafWbNmsWiRYvYsWMH586d448//sDb29tmnCeffJLk5GROnz5N2bJlad26NR988AH+/v4AREdH88477+R7/dKlS3PhwgVje/LkycyYMYO0tDQqVKjAs88+S0xMDK6urrfmDRCHkpOTQ1ZWVmGXIXJHKFWqFCVKlCiQsSy5ubm5BTLSHc5qteLl5UVGRoYmgjugtm3b0qlTJxo3bkx2djZvvfUWe/bsYd++fZQpUwb4K8hcunQJgKioqKuGpkmTJhESEoKfnx+//vorb7zxBgCbN28G4Pz585w/f97mOa1ataJx48bExsYCsGjRIl5++WU++eQTmjVrxsGDB+nZsyedOnVi4sSJt/BdEEeQlZVFamoqOTk5hV2KyB3D29sbX1/fq072tufzW6GpgCg0FS1nzpyhUqVKbNiwgebNm9u0JSQk0LJly6uGpn9atWoVYWFhZGZmUqpUqXztP/30Ew0aNGDjxo08/PDDAAwYMID9+/cTHx9v9Hv99dfZunUr33///c0fnDis3Nxc0tLSuHz5sqmF9ETk5uTm5nLx4kVOnz6Nt7c3fn5++frY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Y8YQFxfHRx99xPTp01myZAk5OTnMmTPHOOszb948vL29SUhIoE2bNowaNYoJEybwzDPPABAYGMi+ffv4z3/+Q48ePVi0aBE5OTnMnTsXV1dX6tSpw/Hjx22CjFmXLl3i008/5a677gLgo48+on379kyYMOGq70HXrl2ZMGECaWlpVKlShZycHBYvXszw4cON98fX15fWrVtTqlQpqlSpwgMPPGB3XQMGDKBjx47AX/NB165dy9y5cxk2bBilSpWymfsZGBhIYmIiS5cutQlNZcuWZdq0aZQoUYJatWrRvn174uPj6d27NwcPHuTrr79m27ZtNG7cGIC5c+cSFBRkU8ffJ4RXq1aNd999l759+xqhKS0tjY4dO9qcpbzVdHlORESuaf/+/WRmZtKqVaubGqNJkyY2+/75jVMzevbsSXJyMjVr1mTgwIF88803+fr8c9yQkBD2798P/HUvyEOHDuHh4YG7uzvu7u6UK1eOS5cucfjwYS5cuMDhw4cJDw832t3d3Xn33Xc5fPiwcSz16tXD1dX1po4FoEqVKkZgyhsnJyeHlJSUq/Zv0KABQUFBLFq0CIANGzZw+vRpnnvuOeCvwPjnn39SvXp1evfuzYoVK8jOzra7rr8fT8mSJbn//vuN9xDg448/plGjRlSsWBF3d3dmzZpFWlqazRh16tSx+SKMn5+fcSZp//79lCxZkkaNGhnttWrVyndJ89tvv6VVq1bcddddeHh40K1bN3777TcuXrwIwMCBA3n33Xd58MEHGTVqFLt27bL7WO2l0CQiIteUtyTHteStOfb3L1vc7K0qrqVhw4akpqYyZswY/vzzT55//nmeffZZ088/f/48jRo1Ijk52eZx8OBBunTpYsyXmT17tk37nj172LJli+nXcXJyyvflk4J6T7p27WqEpkWLFtG2bVvjLF1AQAApKSlMnz4dNzc3+vfvT/PmzQv0v8fixYt54403CA8P55tvviE5OZmXXnqJrKwsm37/vB+nxWIx1rUz4+jRozzxxBPUq1ePL774gqSkJD7++GMA47V69erFkSNH6NatG7t37+b+++/no48+uskjvD6FJhERuaZ77rkHNzc34uPjr9qeN/fo5MmTxr7k5GSbPkFBQWzdutVmnz0h5O88PT154YUXmD17NkuWLOGLL77g999/v+a4W7ZsMS77NGzYkJ9//plK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yWu/GhcXFzw9PW0eIiIiUnwVqdB0/PhxfvvtN/z8/AAICQnh7NmzJCUlGX2+++47cnJyaNKkidFn48aNXL582egTFxdHzZo1KVu2rNEnPj7e5rXi4uIICQm51YckIiIiRUShhqbz58+TnJxMcnIyAKmpqSQnJ5OWlsb58+cZOnQoW7Zs4ejRo8THx/PUU09Ro0YNQkNDAQgKCqJt27b07t2bbdu28cMPPzBgwAA6deqEv78/AF26dMHZ2Znw8HD27t3LkiVLmDJlis2ltddee421a9cyYcIEDhw4QHR0ND/++CMDBgy47e+JiIiIOKZCXXIgISGBli1b5tvfo0cPZsyYQVhYGDt37uTs2bP4+/vTpk0bxowZYzNp+/fff2fAgAGsXr0aJycnOnbsyNSpU3F3dzf67Nq1i4iICLZv306FChV49dVXiYyMtHnNZcuWMXz4cI4ePco999zDuHHjePzxx00fi5YckDuFlhwQkeLEns9vh1mnqahTaJI7hUKTiBQnxWqdJhERERFHoNAkIiIiYoJCk4iIiIgJdoWm7OxsRo8ezfHjx29VPSIiIiIOya7QVLJkScaPH092dvatqkdERETEIdl9ee7RRx9lw4YNt6IWEREREYdl973n2rVrx5tvvsnu3btp1KgRZcqUsWl/8sknC6w4EREREUdh9zpNTk7XPjllsVi4cuXKTRdVFGmdJrlTaJ0mESlO7Pn8tvtMU05Ozg0XJiIiIlJUackBERERERNuKDRt2LCBDh06UKNGDWrUqMGTTz7Jpk2bCro2EREREYdhd2hasGABrVu3pnTp0gwcOJCBAwfi5uZGq1atWLRo0a2oUURERKTQ2T0RPCgoiD59+jB48GCb/RMnTmT27Nns37+/QAssKjQRXO4UmgguIsXJLb1h75EjR+jQoUO+/U8++SSpqan2DiciIiJSJNgdmgICAoiPj8+3/9tvvyUgIKBAihIRERFxNHYvOfD6668zcOBAkpOTadasGQA//PADsbGxTJkypcALFBEREXEEdoemfv364evry4QJE1i6dCnw1zynJUuW8NRTTxV4gSIiIiKOwO7QBPD000/z9NNPF3QtIiIiIg7L7jlN1atX57fffsu3/+zZs1SvXr1AihIRERFxNHaHpqNHj171/nKZmZn8+uuvBVKUiIiIiKMxfXlu1apVxs/r1q3Dy8vL2L5y5Qrx8fFUq1atQIsTERERcRSmQ1NYWBgAFouFHj162LSVKlWKatWqMWHChAItTkRERMRRmA5NOTk5AAQGBrJ9+3YqVKhwy4oSERERcTR2f3tOq36LiIjIncjuieADBw5k6tSp+fZPmzaNQYMGFURNIiIiIg7H7tD0xRdf8OCDD+bb36xZMz7//PMCKUpERETE0dgdmn777Tebb87l8fT05P/+7/8KpCgRERERR2N3aKpRowZr167Nt//rr7/W4pYiIiJSbNk9EXzIkCEMGDCAM2fO8OijjwIQHx/PhAkTmDx5ckHXJyIiIuIQ7A5NL7/8MpmZmbz33nuMGTMGgGrVqjFjxgy6d+9e4AWKiIiIOAK7L88B9OvXj+PHj3Pq1CmsVitHjhy5ocC0ceNGOnTogL+/PxaLhZUrVxptly9fJjIykuDgYMqUKYO/vz/du3fnxIkTNmNUq1YNi8Vi8xg7dqxNn127dvHwww/j6upKQEAA48aNy1fLsmXLqFWrFq6urgQHB/PVV1/ZfTwiIiJSfN1QaMpTsWJF3N3db/j5Fy5coH79+nz88cf52i5evMiOHTsYMWIEO3bsYPny5aSkpPDkk0/m6zt69GhOnjxpPF599VWjzWq10qZNG6pWrUpSUhLjx48nOjqaWbNmGX02b95M586dCQ8PZ+fOnYSFhREWFsaePXtu+NhERESkeLHk5ubm2vukzz//nKVLl5KWlkZWVpZN244dO26sEIuFFStWGLdruZrt27fzwAMP8Msvv1ClShXgrzNNgwYNuuYaUTNmzODtt98mPT0dZ2dnAN58801WrlzJgQMHAHjhhRe4cOECa9asMZ7XtGlTGjRowMyZM03Vb7Va8fLyIiMjA09PT1PPKS6GWgq7Armdxtv9F0NExHHZ8/lt95mmqVOn8tJLL+Hj48POnTt54IEHKF++PEeOHKFdu3Y3XLQZGRkZWCwWvL29bfaPHTuW8uXLc9999zF+/Hiys7ONtsTERJo3b24EJoDQ0FBSUlL4448/jD6tW7e2GTM0NJTExMRr1pKZmYnVarV5iIiISPFld2iaPn06s2bN4qOPPsLZ2Zlhw4YRFxfHwIEDycjIuBU1AnDp0iUiIyPp3LmzTRIcOHAgixcvZv369bzyyiu8//77DBs2zGhPT0/Hx8fHZqy87fT09Ov2yWu/mpiYGLy8vIxHQEDATR+jiIiIOC67vz2XlpZGs2bNAHBzc+PcuXMAdOvWjaZNmzJt2rSCrZC/JoU///zz5ObmMmPGDJu2IUOGGD/Xq1cPZ2dnXnnlFWJiYnBxcSnwWvJERUXZvLbValVwEhERKcbsPtPk6+vL77//DkCVKlXYsmUL8NeNfG9getS/ygtMv/zyC3Fxcf96vbFJkyZkZ2dz9OhRo95Tp07Z9Mnb9vX1vW6fvParcXFxwdPT0+YhIiIixZfdoenRRx9l1apVALz00ksMHjyYxx57jBdeeIGnn366QIvLC0w///wz3377LeXLl//X5yQnJ+Pk5ESlSpUACAkJYePGjVy+fNnoExcXR82aNSlbtqzRJz4+3macuLg4QkJCCvBoREREpCiz+/LcrFmzyMnJASAiIoLy5cuzefNmnnzySV555RW7xjp//jyHDh0ytlNTU0lOTqZcuXL4+fnx7LPPsmPHDtasWcOVK1eMOUblypXD2dmZxMREtm7dSsuWLfHw8CAxMZHBgwfz4osvGoGoS5cuvPPOO4SHhxMZGcmePXuYMmUKkyZNMl73tdde45FHHmHChAm0b9+exYsX8+OPP9osSyAiIiJ3NlNLDjzzzDPExsbi6enJp59+ygsvvFAg84USEhJo2bJlvv09evQgOjqawMDAqz5v/fr1tGjRgh07dtC/f38OHDhAZmYmgYGBdOvWjSFDhtjUt2vXLiIiIti+fTsVKlTg1VdfJTIy0mbMZcuWMXz4cI4ePco999zDuHHjePzxx00fi5YckDuFlhwQkeLEns9vU6HJ2dmZX375BT8/P0qUKMHJkyeNy1/yF4UmuVMoNIlIcWLP57epy3O1atUiKiqKli1bkpuby9KlS685sO4/JyIiIsWRqTNNmzdvZsiQIRw+fJjff/8dDw8PLJb8pxcsFovxzbo7jc40yZ1CZ5pEpDgp8DNNzZo1M5YWcHJy4uDBg7o8JyIiIncUu5ccSE1NpWLFireiFhERERGHZfeSA1WrVr0VdYiIiIg4NLvPNImIiIjciRSaRERERExQaBIRERExwe45TXlOnz5NSkoKADVr1tS36URERKRYs/tM07lz5+jWrRt33XUXjzzyCI888gh33XUXL774IhkZGbeiRhEREZFCZ3do6tWrF1u3bmXNmjWcPXuWs2fPsmbNGn788Ue7b9grIiIiUlTYfXluzZo1rFu3joceesjYFxoayuzZs2nbtm2BFiciIiLiKOw+01S+fHm8vLzy7ffy8qJs2bIFUpSIiIiIo7E7NA0fPpwhQ4aQnp5u7EtPT2fo0KGMGDGiQIsTERERcRR2X56bMWMGhw4dokqVKlSpUgWAtLQ0XFxcOHPmDP/5z3+Mvjt27Ci4SkVEREQKkd2hKSws7BaUISIiIuLY7A5No0aNuhV1iIiIiDg0rQguIiIiYoLdZ5qcnJywWCzXbL9y5cpNFSQiIiLiiOwOTStWrLDZvnz5Mjt37mT+/Pm88847BVaYiIiIiCOxOzQ99dRT+fY9++yz1KlThyVLlhAeHl4ghYmIiIg4kgKb09S0aVPi4+MLajgRERERh1IgoenPP/9k6tSp3HXXXQUxnIiIiIjDsfvyXNmyZW0mgufm5nLu3DlKly7NggULCrQ4EREREUdhd2iaNGmSTWhycnKiYsWKNGnSRPeeExERkWLL7tDUs2fPW1CGiIiIiGMzFZp27dplesB69erdcDEiIiIijspUaGrQoAEWi4Xc3FwALW4pIiIidxxT355LTU3lyJEjpKamsnz5cgIDA5k+fTo7d+5k586dTJ8+nbvvvpsvvvjiVtcrIiIiUihMnWmqWrWq8fNzzz3H1KlTefzxx4199erVIyAggBEjRhAWFlbgRYqIiIgUNrvXadq9ezeBgYH59gcGBrJv3z67xtq4cSMdOnTA398fi8XCypUrbdpzc3MZOXIkfn5+uLm50bp1a37++WebPr///jtdu3bF09MTb29vwsPDOX/+vE2fXbt28fDDD+Pq6kpAQADjxo3LV8uyZcuoVasWrq6uBAcH89VXX9l1LCIiIlK82R2agoKCiImJISsry9iXlZVFTEwMQUFBdo114cIF6tevz8cff3zV9nHjxjF16lRmzpzJ1q1bKVOmDKGhoVy6dMno07VrV/bu3UtcXBxr1qxh48aN9OnTx2i3Wq20adOGqlWrkpSUxPjx44mOjmbWrFlGn82bN9O5c2fCw8PZuXMnYWFhhIWFsWfPHruOR0RERIovS27e7G6Ttm3bRocOHcjNzTW+Kbdr1y4sFgurV6/mgQceuLFCLBZWrFhhXN7Lzc3F39+f119/nTfeeAOAjIwMfHx8iI2NpVOnTuzfv5/atWuzfft27r//fgDWrl3L448/zvHjx/H392fGjBm8/fbbpKen4+zsDMCbb77JypUrOXDgAAAvvPACFy5cYM2aNUY9TZs2pUGDBsycOdNU/VarFS8vLzIyMvD09Lyh96CoGnrt7wVIMTTerr8YIiKOzZ7Pb7vPND3wwAMcOXKEd999l3r16lGvXj3ee+89jhw5csOB6WpSU1NJT0+ndevWxj4vLy+aNGlCYmIiAImJiXh7exuBCaB169Y4OTmxdetWo0/z5s2NwAQQGhpKSkoKf/zxh9Hn76+T1yfvda4mMzMTq9Vq8xAREZHiy+7FLQHKlCljcwnsVkhPTwfAx8fHZr+Pj4/Rlp6eTqVKlWzaS5YsSbly5Wz6/HMOVt6Y6enplC1blvT09Ou+ztXExMTwzjvv3MCRiYiISFF0Qzfs/e9//8tDDz2Ev78/v/zyC/DX7VX+97//FWhxjiwqKoqMjAzjcezYscIuSURERG4hu0PTjBkzGDJkCO3ateOPP/4wFrMsW7YskydPLrDCfH19ATh16pTN/lOnThltvr6+nD592qY9Ozub33//3abP1cb4+2tcq09e+9W4uLjg6elp8xAREZHiy+7Q9NFHHzF79mzefvttSpb8/1f37r//fnbv3l1ghQUGBuLr60t8fLyxz2q1snXrVkJCQgAICQnh7NmzJCUlGX2+++47cnJyaNKkidFn48aNXL582egTFxdHzZo1jRsMh4SE2LxOXp+81xERERGxOzSlpqZy33335dvv4uLChQsX7Brr/PnzJCcnk5ycbIydnJxMWloaFouFQYMG8e6777Jq1Sp2795N9+7d8ff3N75hFxQURNu2benduzfbtm3jhx9+YMCAAXTq1Al/f38AunTpgrOzM+Hh4ezdu5clS5YwZcoUhgwZYtTx2muvsXbtWiZMmMCBAweIjo7mxx9/ZMCAAfa+PSIiIlJM2T0RPDAwkOTkZJtVwuGvr/rbu07Tjz/+SMuWLY3tvCDTo0cPYmNjGTZsGBcuXKBPnz6cPXuWhx56iLVr1+Lq6mo8Z+HChQwYMIBWrVrh5OREx44dmTp1qtHu5eXFN998Q0REBI0aNaJChQqMHDnSZiJ7s2bNWLRoEcOHD+ett97innvuYeXKldStW9eu4xEREZHiy+51mubMmUN0dDQTJkwgPDycOXPmcPjwYWJiYpgzZw6dOnW6VbU6NK3TJHcKrdMkIsWJPZ/fdp9p6tWrF25ubgwfPpyLFy/SpUsX/P39mTJlyh0bmERERKT4u6F1mrp27UrXrl25ePEi58+fz7dWkoiIiEhxc0PrNGVnZ/Ptt9/y3//+Fzc3NwBOnDiR70a5IiIiIsWF3WeafvnlF9q2bUtaWhqZmZk89thjeHh48MEHH5CZmWn6Xm0iIiIiRYndZ5pee+017r//fv744w/jLBPA008/nW+tIxEREZHiwu4zTZs2bWLz5s02N8AFqFatGr/++muBFSYiIiLiSOw+05STk2PcOuXvjh8/joeHR4EUJSIiIuJo7A5Nbdq0sbnHnMVi4fz584waNYrHH3+8IGsTERERcRh2X56bMGECoaGh1K5dm0uXLtGlSxd+/vlnKlSowGeffXYrahQREREpdHaHpsqVK/PTTz+xePFidu3axfnz5wkPD6dr1642E8NFREREipMbWtyyZMmSvPjiiwVdi4iIiIjDuqHQlJKSwkcffcT+/fsBCAoKYsCAAdSqVatAixMRERFxFHZPBP/iiy+oW7cuSUlJ1K9fn/r167Njxw6Cg4P54osvbkWNIiIiIoXO7jNNw4YNIyoqitGjR9vsHzVqFMOGDaNjx44FVpyIiIiIo7D7TNPJkyfp3r17vv0vvvgiJ0+eLJCiRERERByN3aGpRYsWbNq0Kd/+77//nocffrhAihIRERFxNHZfnnvyySeJjIwkKSmJpk2bArBlyxaWLVvGO++8w6pVq2z6ioiIiBQHltzc3Fx7nuDkZO7klMViuertVoorq9WKl5cXGRkZeHp6FnY5t9VQS2FXILfTeLv+YoiIODZ7Pr/tPtOUk5Nzw4WJiIiIFFV2z2kSERERuROZDk2JiYmsWbPGZt+nn35KYGAglSpVok+fPmRmZhZ4gSIiIiKOwHRoGj16NHv37jW2d+/eTXh4OK1bt+bNN99k9erVxMTE3JIiRURERAqb6dCUnJxMq1atjO3FixfTpEkTZs+ezZAhQ5g6dSpLly69JUWKiIiIFDbToemPP/7Ax8fH2N6wYQPt2rUzths3bsyxY8cKtjoRERERB2E6NPn4+JCamgpAVlYWO3bsMNZpAjh37hylSpUq+ApFREREHIDp0PT444/z5ptvsmnTJqKioihdurTNCuC7du3i7rvvviVFioiIiBQ20+s0jRkzhmeeeYZHHnkEd3d35s+fj7Ozs9H+ySef0KZNm1tSpIiIiEhhMx2aKlSowMaNG8nIyMDd3Z0SJUrYtC9btgx3d/cCL1BERETEEdi9IriXl9dV95crV+6mixERERFxVFoRXERERMQEhSYRERERExw+NFWrVg2LxZLvERERAUCLFi3ytfXt29dmjLS0NNq3b0/p0qWpVKkSQ4cOJTs726ZPQkICDRs2xMXFhRo1ahAbG3u7DlFERESKALvnNN1u27dv58qVK8b2nj17eOyxx3juueeMfb1792b06NHGdunSpY2fr1y5Qvv27fH19WXz5s2cPHmS7t27U6pUKd5//30AUlNTad++PX379mXhwoXEx8fTq1cv/Pz8CA0NvQ1HKSIiIo7O4UNTxYoVbbbHjh3L3XffzSOPPGLsK126NL6+vld9/jfffMO+ffv49ttv8fHxoUGDBowZM4bIyEiio6NxdnZm5syZBAYGMmHCBACCgoL4/vvvmTRp0jVDU2Zmps0Niq1W680eqoiIiDgwh78893dZWVksWLCAl19+GYvFYuxfuHAhFSpUoG7dukRFRXHx4kWjLTExkeDgYJtbwISGhmK1Wo0bECcmJtK6dWub1woNDSUxMfGatcTExODl5WU8AgICCuowRURExAE5/Jmmv1u5ciVnz56lZ8+exr4uXbpQtWpV/P392bVrF5GRkaSkpLB8+XIA0tPTbQITYGynp6dft4/VauXPP//Ezc0tXy1RUVEMGTLE2LZarQpOIiIixViRCk1z586lXbt2+Pv7G/v69Olj/BwcHIyfnx+tWrXi8OHDt/S2Li4uLri4uNyy8UVERMSxFJnLc7/88gvffvstvXr1um6/Jk2aAHDo0CEAfH19OXXqlE2fvO28eVDX6uPp6XnVs0wiIiJy5ykyoWnevHlUqlSJ9u3bX7dfcnIyAH5+fgCEhISwe/duTp8+bfSJi4vD09OT2rVrG33i4+NtxomLiyMkJKQAj0BERESKsiIRmnJycpg3bx49evSgZMn/f0Xx8OHDjBkzhqSkJI4ePcqqVavo3r07zZs3p169egC0adOG2rVr061bN3766SfWrVvH8OHDiYiIMC6v9e3blyNHjjBs2DAOHDjA9OnTWbp0KYMHDy6U4xURERHHUyRC07fffktaWhovv/yyzX5nZ2e+/fZb2rRpQ61atXj99dfp2LEjq1evNvqUKFGCNWvWUKJECUJCQnjxxRfp3r27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uHjxotGWmJhIcHCwEZgAQkNDsVqt7N271+jTunVrmzFDQ0NJTEy8Zi2ZmZlYrVabh4iIiBRfJQu7ALNycnIYNGgQDz74IHXr1jX2d+nShapVq+Lv78+uXbuIjIwkJSWF5cuXA5Cenm4TmABjOz09/bp9rFYrf/75J25ubvnqiYmJ4Z133inQYxQRERHHVWRCU0REBHv27OH777+32d+nTx/j5+DgYPz8/GjVqhWHDx/m7rvvvmX1REVFMWTIEGPbarUSEBBwy15PRERECleRuDw3YMAA1qxZw/r166lcufJ1+zZp0gSAQ4cOAeDr68upU6ds+uRt+/r6XrePp6fnVc8yAbi4uODp6WnzEBERkeLLoUNTbm4uAwYMYMWKFXz33XcEBgb+63OSk5MB8PPzAyAkJITdu3dz+vRpo09cXByenp7Url3b6BMfH28zTlxcHCEhIQV0JCIiIlLUOXRoioiIYMGCBSxatAgPDw/S09NJT0/nzz//BODw4cOMGTOGpKQkjh49yqpVq+jevTvNmzenXr16ALRp04batWvTrVs3fvrpJ9atW8fw4cOJiIjAxcUFgL59+3LkyBGGDRvGgQMHmD59OkuXLmXw4MGFduwiIiLiWBx6yQGL5erfZZ83bx49e/bk2LFjvPjii+zZs4cLFy4QEBDA008/zfDhw20ul/3yyy/069ePhIQEypQpQ48ePRg7diwlS/7/KV0JCQkMHjyYffv2UblyZUaMGEHPnj1N16olB+ROoSUHRKQ4sefz26FDU1Gi0CR3CoUmESlOiu06TSIiIiKFRaFJRERExASFJhERERETFJpERERETFBoEhERETFBoUlERETEBIUmERERERMUmkRERERMUGgSERERMUGhSURERMQEhSYRERERExSaRERERExQaBIRERExQaFJRERExASFJhERERETFJpERERETFBoEhERETFBoUlERETEBIUmERERERMUmkRERERMUGgSERERMUGhSURE7jgzZsygXr16eHp64unpSUhICF9//TUAR48exWKxXPWxbNmyQq5cClPJwi5ARETkdqtcuTJjx47lnnvuITc3l/nz5/PUU0+xc+dOatWqxcmTJ236z5o1i/Hjx9OuXbtCqlgcgUKTiIjccTp06GCz/d577zFjxgy2bNlCnTp18PX1tWlfsWIFzz//PO7u7rezTHEwujwnIiJ3tCtXrrB48WIuXLhASEhIvvakpCSSk5MJDw8vhOrEkehMk4iI3JF2795NSEgIly5dwt3dnRUrVlC7du18/ebOnUtQUBDNmjUrhCrFkehMk4iI3JFq1qxJcnIyW7dupV+/fvTo0YN9+/bZ9Pnzzz9ZtGiRzjIJoDNNIiJyh3J2dqZGjRoANGrUiO3btzNlyhT+85//GH0+//xzLl68SPfu3QurTHEgOtMkIiIC5OTkkJmZabNv7ty5PPnkk1SsWLGQqhJHojNNIiJyx4mKiqJdu3ZUqVKFc+fOsWjRIhISEli3bp3R59ChQ2zcuJGvvvqqECsVR6LQJCIid5zTp0/TvXt3Tp48iZeXF/Xq1WPdunU89thjRp9PPvmEypUr06ZNm0KsVByJJTc3N7ewiygOrFYrXl5eZGRk4OnpWdjl3FZDLYVdgdxO4/UXQ0SKEXs+vzWnSURERMQEXZ4rIHkn7KxWayFXcvtl/nsXKUbuwP/F72jDvQq7Armd3s0o7Apuv7zPbTMX3nR5roAcP36cgICAwi5DREREbsCxY8eoXLnydfsoNBWQnJwcTpw4gYeHBxaLJvkUd1arlYCAAI4dO3bHzWETKe70+31nyc3N5dy5c/j7++PkdP1ZS7o8V0CcnJz+NaFK8ePp6ak/qiLFlH6/7xxeXuauQ2siuIiIiIgJCk0iIiIiJig0idwAFxcXRo0ahYuLS2GXIiIFTL/fci2aCC4iIiJigs40iYiIiJig0CQiIiJigkKTiIiIiAkKTSKFrFq1akyePLmwyxCRv2nRogWDBg0q7DLEwSg0SbHRs2dPLBYLY8eOtdm/cuVKh16lffv27fTp06ewyxAp8s6cOUO/fv2oUqUKLi4u+Pr6Ehoayg8//ACAxWJh5cqVpsZavnw5Y8aMuYXVSlGkFcGlWHF1deWDDz7glVdeoWzZsoVdznVlZWXh7OxMxYoVC7sUkWKhY8eOZGVlMX/+fKpXr86pU6eIj4/nt99+Mz1G3u9luXLlbmGlUlTpTJMUK61bt8bX15eYmJirtkdHR9OgQQObfZMnT6ZatWrGds+ePQkLC+P999/Hx8cHb29vRo8eTXZ2NkOHDqVcuXJUrlyZefPm2Yxz7Ngxnn/+eby9vSlXrhxPPfUUR48ezTfue++9h7+/PzVr1gTyX547e/Ysr7zyCj4+Pri6ulK3bl3WrFlzU++LSHF39uxZNm3axAcffEDLli2pWrUqDzzwAFFRUTz55JPG7/jTTz+NxWIxtvP+JsyZM4fAwEBcXV2B/JfnqlWrxvvvv8/LL7+Mh4cHVapUYdasWTY1bN68mQYNGuDq6sr9999vnOVOTk6+De+A3A4KTVKslChRgvfff5+PPvqI48eP3/A43333HSdOnGDjxo1MnDiRUaNG8cQTT1C2bFm2bt1K3759eeWVV4zXuHz5MqGhoXh4eLBp0yZ++OEH3N3dadu2LVlZWca48fHxpKSkEBcXd9UglJOTQ7t27fjhhx9YsGAB+/btY+zYsZQoUeKGj0XkTuDu7o67uzsrV64kMzMzX/v27dsBmDdvHidPnjS2AQ4dOsQXX3zB8uXLrxtwJkyYwP3338/OnTvp378//fr1IyUlBfjrJr8dOnQgODiYHTt2MGbMGCIjIwv2IKXQ6fKcFDtPP/00DRo0YNSoUcydO/eGxihXrhxTp07FycmJmjVrMm7cOC5evMhbb70FQFRUFGPHjuX777+nU6dOLFmyhJycHObMmWPMn5o3bx7e3t4kJCTQpk0bAMqUKcOcOXNwdna+6ut+++23bNu2jf3793PvvfcCUL169Rs6BpE7ScmSJYmNjaV3797MnDmThg0b8sgjj9CpUyfq1atnXAb39vbG19fX5rlZWVl8+umn/3qp/PHHH6d///4AREZGMmnSJNavX0/NmjVZtGgRFouF2bNn4+rqSu3atfn111/p3bv3rTlgKRQ60yTF0gcffMD8+fPZv3//DT2/Tp06ODn9/18PHx8fgoODje0SJUpQvnx5Tp8+DcBPP/3EoUOH8PDwMP7FW65cOS5dusThw4eN5wUHB18zMAEkJydTuXJlIzCJiHkdO3bkxIkTrFq1irZt25KQkEDDhg2JjY297vOqVq1qam5hvXr1jJ8tFgu+vr7G34CUlBTq1atnXN4DeOCBB27sQMRh6UyTFEvNmzcnNDSUqKgoevbsaex3cnLin3cOunz5cr7nlypVymbbYrFcdV9OTg4A58+fp1GjRixcuDDfWH//Y1ymTJnr1u3m5nbddhG5PldXVx577DEee+wxRowYQa9evRg1apTN34F/+rffyzzX+xsgdwadaZJia+zYsaxevZrExERjX8WKFUlPT7cJTgUxSbNhw4b8/PPPVKpUiRo1atg8vLy8TI9Tr149jh8/zsGDB2+6JhGB2rVrc+HCBeCv0HPlypVb8jo1a9Zk9+7dNvOp/j5vSooHhSYptoKDg+natStTp0419rVo0YIzZ84wbtw4Dh8+zMcff8zXX39906/VtWtXKlSowFNPPcWmTZtITU0lISGBgQMH2jUh/ZFHHqF58+Z07NiRuLg4UlNT+frrr1m7du1N1yhSnP322288+uijLFiwgF27dpGamsqyZcsYN24cTz31FPDXN+Di4+NJT0/njz/+KNDX79KlCzk5OfTp04f9+/ezbt06PvzwQwCHXidO7KPQJMXa6NGjbU6fBwUFMX36dD7++GPq16/Ptm3beOONN276dUqXLs3GjRupUqUKzzzzDEFBQYSHh3Pp0iU8PT3tGuuLL76gcePGdO7cmdq1azNs2LBb9q9jkeLC3d2dJk2aMGnSJJo3b07dunUZMWIEvXv3Ztq0acBf336Li4sjICCA++67r0Bf39PTk9WrV5OcnEyDBg14++23GTlyJIDNPCcp2iy5/5zgISIiIjdt4cKFvPTSS2RkZGi+YjGhieAiIiIF4NNPP6V69ercdddd/PTTT0RGRvL8888rMBUjCk0iIiIFID09nZEjR5Keno6fnx/PPfcc7733XmGXJQVIl+dERERETNBEcBERERETFJpERERETFBoEhERETFBoUlERETEBIUmERERERMUmkREClh0dDQNGjS46XGqVavG5MmTb3ocESkYCk0iUmS0aNGCQYMG5dsfGxuLt7e3sR0dHY3FYsFisVCyZEkqVKhA8+bNmTx5ss0NVfPGzOv790d2dvZVa/jnawHs37+fgIAAnnvuObKysnjjjTeIj4+/2cMVEQej0CQixVKdOnU4efIkaWlprF+/nueee46YmBiaNWvGuXPnbPr27t2bkydP2jxKljS39u/27dt5+OGHadu2LUuWLMHZ2Rl3d3fKly9/Kw5LRAqRQpOIFEslS5bE19cXf39/goODefXVV9mwYQN79uzhgw8+sOlbunRpfH19bR5mfPfddzz66KOEh4cze/ZsnJz++pP6z8tzPXv2JCwsjA8//BA/Pz/Kly9PREQEly9fNvqcPn2aDh064ObmRmBgIAsXLrz5N0FECpRCk4jcMWrVqkW7du1Yvnz5TY+1YsUK2rdvz/Dhw/OFsKtZv349hw8fZv369cyfP5/Y2FhiY2ON9p49e3Ls2DHWr1/P559/zvTp0zl9+vRN1ykiBUehSUTuKLVq1eLo0aM2+6ZPn467u7vxeP311687xvnz53nuuecYOnQokZGRpl63bNmyTJs2jVq1avHEE0/Qvn17Y97TwYMH+frrr5k9ezZNmzalUaNGzJ07lz///POGjlFEbg3dsFdE7ii5ublYLBabfV27duXtt982tv850fuf3NzceOihh5g9ezadO3cmKCjoX1+3Tp06lChRwtj28/Nj9+7dwF8TyUuWLEmjRo2M9lq1av1rHSJye+lMk4gUGZ6enmRkZOTbf/bsWby8vEyNsX//fgIDA232eXl5UaNGDeNRoUKF645RokQJVq5cScOGDWnZsiX79+//19ctVaqUzbbFYiEnJ8dUzSLiGBSaRKTIqFmzJjt27Mi3f8eOHdx7773/+vwDBw6wdu1aOnbseNO1uLi4sHz5cho3bkzLli3Zt2/fDY9Vq1YtsrOzSUpKMvalpKRw9uzZm65TRAqOQpOIFBn9+vXj4MGDDBw4kF27dpGSksLEiRP57LPP8s1Dys7OJj09nRMnTrB7924++ugjHnnkERo0aMDQoUMLpB4XFxe++OILmjRpQsuWLdm7d+8NjVOzZk3atm3LK6+8wtatW0lKSqJXr164ubkVSJ0iUjAUmkSkyKhevTobN27kwIEDtG7dmiZNmrB06VKWLVtG27Ztbfru3bsXPz8/qlSpQosWLVi6dClRUVFs2rQJd3f3AqvJ2dmZzz//nGbNmtGyZUv27NlzQ+PMmzcPf39/HnnkEZ555hn69OlDpUqVCqxOEbl5ltzc3NzCLkJERETE0elMk4iIiIgJCk0iIiIiJig0iYiIiJig0CQiIiJigkKTiIiIiAkKTSIiIiImKDSJiIiImKDQJCIiImKCQpOIiIiICQpNIiIiIiYoNImIiIiY8P8AmHo6JI0unIIAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "performance_plot(performance_df, xlabel=\"UDF Kind\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## UDF Performance in GroupBy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_rows = 100_000_000\n",
    "timeit_number = 10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "pdf = pd.DataFrame()\n",
    "pdf[\"key\"] = rng.integers(0, 2, num_rows)\n",
    "pdf[\"val\"] = rng.integers(0, 7, num_rows)\n",
    "\n",
    "\n",
    "def custom_formula_udf(df):\n",
    "    df[\"out\"] = df[\"key\"] * df[\"val\"] - 10\n",
    "    return df\n",
    "\n",
    "\n",
    "gdf = cudf.from_pandas(pdf)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
      "/tmp/ipykernel_948063/2864685541.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.\n",
      "  lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n"
     ]
    }
   ],
   "source": [
    "pandas_udf_groupby, cudf_udf_groupby = timeit_pandas_cudf(\n",
    "    pdf,\n",
    "    gdf,\n",
    "    lambda df: df.groupby([\"key\"], group_keys=False).apply(custom_formula_udf),\n",
    "    number=timeit_number,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>cudf speedup vs. pandas</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Grouped UDF</th>\n",
       "      <td>88.879055</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             cudf speedup vs. pandas\n",
       "Grouped UDF                88.879055"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "performance_df = pd.DataFrame(\n",
    "    {\"cudf speedup vs. pandas\": [pandas_udf_groupby / cudf_udf_groupby]},\n",
    "    index=[\"Grouped UDF\"],\n",
    ")\n",
    "performance_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGdCAYAAAAIbpn/AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAzi0lEQVR4nO3deVzU1f7H8fewCMgy7iwFgmlCpllabt1WS83cb9aVSk3TmxCZpem9ouYSWW5hico1l3LJJUutbOGq5Z4oikmAioIp2vUquCSofH9/+HNuE2qMgsNXX8/HYx4PON/vnPnM+NB5e77ne47FMAxDAAAAJuTi7AIAAACuFkEGAACYFkEGAACYFkEGAACYFkEGAACYFkEGAACYFkEGAACYFkEGAACYlpuzCyhrRUVFOnjwoHx9fWWxWJxdDgAAKAHDMHTixAkFBQXJxeXy4y43fJA5ePCggoODnV0GAAC4Cjk5Obr11lsve/yGDzK+vr6SLnwQfn5+Tq4GAACURH5+voKDg23f45dzwweZi5eT/Pz8CDIAAJjMn00LYbIvAAAwLYIMAAAwLYIMgHLt/Pnzio2NVVhYmLy8vHTbbbdp1KhRMgzDds7hw4fVo0cPBQUFqWLFimrdurUyMzOdWDWA6+WGnyMDwNzGjh2rhIQEzZ49W/Xq1dOWLVvUs2dPWa1WxcTEyDAMdezYUe7u7vr888/l5+enCRMmqGXLltq1a5e8vb1L9DqGYejcuXM6f/58Gb8jAJLk6uoqNze3a14ahSADoFxbv369OnTooLZt20qSQkNDNX/+fG3evFmSlJmZqY0bN2rnzp2qV6+eJCkhIUEBAQGaP3++evfu/aevUVhYqEOHDun06dNl90YAFFOxYkUFBgaqQoUKV90HQQZAuda8eXNNnz5dGRkZuv3227V9+3atXbtWEyZMkCQVFBRIkjw9PW3PcXFxkYeHh9auXfunQaaoqEhZWVlydXVVUFCQKlSowOKZQBkzDEOFhYX69ddflZWVpTp16lxx0bsrIcgAKNcGDx6s/Px8hYeHy9XVVefPn9eYMWMUGRkpSQoPD1dISIiGDBmiadOmydvbWxMnTtSBAwd06NChP+2/sLBQRUVFCg4OVsWKFcv67QD4f15eXnJ3d9f+/ftVWFho958RRzDZF0C5tnDhQs2dO1fz5s3T1q1bNXv2bI0bN06zZ8+WJLm7u+vTTz9VRkaGqlSpoooVK2rVqlVq06aNQ//Du9r/DQK4eqXx944RGQDl2sCBAzV48GA988wzkqT69etr//79iouLU/fu3SVJjRo1UkpKivLy8lRYWKjq1aurSZMmaty4sTNLB3Ad8F8QAOXa6dOni/2vzdXVVUVFRcXOtVqtql69ujIzM7VlyxZ16NDhepV5w5g1a5YqVapk1zZ9+nQFBwfLxcVFkyZNckpdV2Pfvn2yWCxKSUlxdimmFhoaWq7/3BmRAVCutWvXTmPGjFFISIjq1aunbdu2acKECXrhhRds5yxatEjVq1dXSEiIUlNT9corr6hjx456/PHHr+m1B17nOb/vGn9+zvWWn5+v6OhoTZgwQV26dJHVanV2SYAdggyAcm3y5MmKjY1Vv379dOTIEQUFBalv374aNmyY7ZxDhw5pwIABOnz4sAIDA/X8888rNjbWiVXfOLKzs3X27Fm1bdtWgYGBzi4HKIZLSwDKNV9fX02aNEn79+/Xb7/9pj179mj06NF2607ExMQoJydHhYWF2r9/v0aNGnVN61KYRVFRkd555x3Vrl1bHh4eCgkJ0ZgxYyRJq1evlsVi0fHjx23np6SkyGKxaN++fba2WbNmKSQkRBUrVlSnTp109OhRu2P169eXJNWqVavYcy8qLCxUdHS0AgMD5enpqZo1ayouLs523GKxKCEhQW3atJGXl5dq1aqlxYsX2/WRk5Ojrl27qlKlSqpSpYo6dOhQ7LX+9a9/KSIiQp6engoPD9eUKVPsjm/evFl33323PD091bhxY23bts3u+KUum3322Wd2t9uPGDFCDRs21LRp02x3snXt2lV5eXnF3rd04c/g1ltvVUJCgl37tm3b5OLiov3798swDI0YMUIhISHy8PBQUFCQYmJiLtnfpVy8RLZgwQI1b95cnp6euvPOO7VmzRrbOefPn1evXr1sK2DXrVtX7733nl0/PXr0UMeOHTVu3DgFBgaqatWqioqK0tmzZ23nHDlyRO3atZOXl5fCwsI0d+7cYvVMmDBB9evXl7e3t4KDg9WvXz+dPHnSdnz//v1q166dKleuLG9vb9WrV09ffvllid+vowgyAGBSQ4YM0dtvv63Y2Fjt2rVL8+bNk7+/f4mfv2nTJvXq1UvR0dFKSUnRww8/rNGjR9uOP/300/ruu+8kXQgJhw4dUnBwcLF+4uPjtWzZMi1cuFDp6emaO3euQkND7c6JjY1Vly5dtH37dkVGRuqZZ55RWlqaJOns2bNq1aqVfH199cMPP2jdunXy8fFR69atVVhYKEmaO3euhg0bpjFjxigtLU1vvfWWYmNjbXevnTx5Uk8++aTuuOMOJScna8SIEXr99dcd+jwv2r17txYuXKjly5dr5cqV2rZtm/r163fJc11cXPS3v/1N8+bNs2ufO3euWrRooZo1a2rJkiWaOHGipk2bpszMTH322We2gOiIgQMH6rXXXtO2bdvUrFkztWvXzhY8LwaqRYsWadeuXRo2bJj+8Y9/aOHChXZ9rFq1Snv27NGqVas0e/ZszZo1S7NmzbId79Gjh3JycrRq1SotXrxYU6ZM0ZEjR4q95/j4eP3000+aPXu2/v3vf2vQoEG241FRUSooKND333+v1NRUjR07Vj4+Pg6/35Li0hJuONd7XgPMzbem1HKq5HnK+f8g5mwp+bknT53Qe5Pe08iB7+uRet2lY1JNz9tUs+H9ytkiHUm/cN6BbdIJ3ws/5/5/28Edkut/pLjh7+mhZq31t0cGSflSp+a367t712vNhpX/X4uXzuZWlSSdPVRdZy0BOnigeC0/bclWsH8d1fS8X5ZfLarpWVM169i/nzYPPaVWDXtL+VKfDqP0xWffKi52ssYMnqJPv/xEhaeLNPzv/5KlwCIVSCOjZ+rOhytp0fTVeqDp4/rn4OH6R9R4de7cWZIUFhamXbt2adq0aerevbvmzZunoqIizZgxQ56enqpXr54OHDigl156ybE/BElnzpzRnDlzdMstt0i6cHmzbdu2Gj9+vAICAoqdHxkZqfHjxys7O1shISEqKirSggULNHToUEkXLs8FBASoZcuWcnd3V0hIiO677z6H64qOjlaXLl0kXVi9euXKlZoxY4YGDRokd3d3vfnmm7Zzw8LCtGHDBi1cuFBdu3a1tVeuXFnvv/++XF1dFR4errZt2yopKUkvvviiMjIy9NVXX2nz5s269957JUkzZsxQRESEXR39+/e3/RwaGqrRo0fr73//u22ELDs7W126dLEbzStLjMgAgAnt3pemgsICtbj30avvIytNDes1sWu7p34zh/t56ske2pWZoof+WlfDxsXo+43fFDvnj/3eU7+Zdu+7MCKTlrld+w7sVsSDvgp/wEfhD/io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v18rV66Up6enEysHAADlgcX4/TK6N6D8/HxZrVbl5eXJz8/P2eXgOhhocXYFAMrKuzf0NxZ+r6Tf3+y1BAAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATIsgAwAATMvpQeaXX37Rs88+q6pVq8rLy0v169fXli1bbMcNw9CwYcMUGBgoLy8vtWzZUpmZmU6sGAAAlBdODTLHjh1TixYt5O7urq+++kq7du3S+PHjVblyZds577zzjuLj4zV16lRt2rRJ3t7eatWqlc6cOePEygEAQHng5swXHzt2rIKDgzVz5kxbW1hYmO1nwzA0adIkDR06VB06dJAkzZkzR/7+/vrss8/0zDPPXPeaAQBA+eHQiMy5c+c0cuRIHThwoFRefNmyZWrcuLGeeuop1ahRQ3fffbcSExNtx7OyspSbm6uWLVva2qxWq5o0aaINGzZcss+CggLl5+fbPQAAwI3JoSDj5uamd999V+fOnSuVF9+7d68SEhJUp04dff3113rppZcUExOj2bNnS5Jyc3MlSf7+/nbP8/f3tx37o7i4OFmtVtsjODi4VGoFAADlj8NzZB555BGtWbOmVF68qKhI99xzj9566y3dfffd6tOnj1588UVNnTr1qvscMmSI8vLybI+cnJxSqRUAAJQ/Ds+RadOmjQYPHqzU1FQ1atRI3t7edsfbt29f4r4CAwN1xx132LVFRERoyZIlkqSAgABJ0uHDhxUYGGg75/Dhw2rYsOEl+/Tw8JCHh0eJawAAAOblcJDp16+fJGnChAnFjlksFp0/f77EfbVo0ULp6el2bRkZGapZs6akCxN/AwIClJSUZAsu+fn52rRpk1566SVHSwcAADcYh4NMUVFRqb34q6++qubNm+utt95S165dtXnzZk2fPl3Tp0+XdCEY9e/fX6NHj1adOnUUFham2NhYBQUFqWPHjqVWBwAAMCen3n597733aunSpRoyZIhGjhypsLAwTZo0SZGRkbZzBg0apFOnTqlPnz46fvy47r//fq1cuVKenp5OrBwAAJQHFsMwDEeftGbNGo0bN05paWmSpDvuuEMDBw7UX/7yl1Iv8Frl5+fLarUqLy9Pfn5+zi4H18FAi7MrAFBW3nX4GwtmVdLvb4fvWvr444/VsmVLVaxYUTExMYqJiZGXl5ceffRRzZs375qKBgAAcITDIzIRERHq06ePXn31Vbv2CRMmKDEx0TZKU14wInPzYUQGuHExInPzKLMRmb1796pdu3bF2tu3b6+srCxHuwMAALhqDgeZ4OBgJSUlFWv/7rvvWEUXAABcVw7ftfTaa68pJiZGKSkpat68uSRp3bp1mjVrlt57771SLxAAAOByHA4yL730kgICAjR+/HgtXLhQ0oV5M5988olth2oAAIDr4arWkenUqZM6depU2rUAAAA4xOE5MrVq1dLRo0eLtR8/fly1atUqlaIAAABKwuEgs2/fvkvup1RQUKBffvmlVIoCAAAoiRJfWlq2bJnt56+//lpWq9X2+/nz55WUlKTQ0NBSLQ4AAOBKShxkLm7SaLFY1L17d7tj7u7uCg0N1fjx40u1OAAAgCspcZC5uOt1WFiYfvzxR1WrVq3MigIAACgJh+9aYvVeAABQXjg82TcmJkbx8fHF2t9//33179+/NGoCAAAoEYeDzJIlS9SiRYti7c2bN9fixYtLpSgAAICScDjIHD161O6OpYv8/Pz0n//8p1SKAgAAKAmHg0zt2rW1cuXKYu1fffUVC+IBAIDryuHJvgMGDFB0dLR+/fVXPfLII5KkpKQkjR8/XpMmTSrt+gAAAC7L4SDzwgsvqKCgQGPGjNGoUaMkSaGhoUpISNDzzz9f6gUCAABcjsUwDONqn/zrr7/Ky8tLPj4+pVlTqcrPz5fValVeXp78/PycXQ6ug4EWZ1cAoKy8e9XfWDCbkn5/X9Xu1xdVr179Wp4OAABwTa4qyCxevFgLFy5Udna2CgsL7Y5t3bq1VAoDAAD4Mw7ftRQfH6+ePXvK399f27Zt03333aeqVatq7969atOmTVnUCAAAcEkOB5kpU6Zo+vTpmjx5sipUqKBBgwbp22+/VUxMjPLy8sqiRgAAgEtyOMhkZ2erefPmkiQvLy+dOHFCkvTcc89p/vz5pVsdAADAFTgcZAICAvTf//5XkhQSEqKNGzdKurCZ5DXcAAUAAOAwh4PMI488omXLlkmSevbsqVdffVWPPfaYnn76aXXq1KnUCwQAALgch+9amj59uoqKiiRJUVFRqlq1qtavX6/27durb9++pV4gAADA5ZRoRKZz587Kz8+XJH388cc6f/687dgzzzyj+Ph4vfzyy6pQoULZVAkAAHAJJQoyK1as0KlTpyRduJzE3UkAAKA8KNGlpfDwcA0ZMkQPP/ywDMPQwoULL7tcMPstAQCA66VEey2tX79eAwYM0J49e/Tf//5Xvr6+sliKb2hjsVhsdzSVF+y1dPNhryXgxsVeSzePUt1rqXnz5rbbrF1cXJSRkaEaNWqUTqUAAABXyeHbr7OystgsEgAAlAsO335ds2bNsqgDAADAYQ6PyAAAAJQXBBkAAGBaBBkAAGBaDs+RuejIkSNKT0+XJNWtW5e7mAAAwHXn8IjMiRMn9Nxzz+mWW27Rgw8+qAcffFC33HKLnn32WVb8BQAA15XDQaZ3797atGmTVqxYoePHj+v48eNasWKFtmzZwqaRAADgunL40tKKFSv09ddf6/7777e1tWrVSomJiWrdunWpFgcAAHAlDo/IVK1aVVartVi71WpV5cqVS6UoAACAknA4yAwdOlQDBgxQbm6urS03N1cDBw5UbGxsqRYHAABwJQ5fWkpISNDu3bsVEhKikJAQSVJ2drY8PDz066+/atq0abZzt27dWnqVAgAA/IHDQaZjx45lUAYAAIDjHA4yw4cPL4s6AAAAHMbKvgAAwLQcHpFxcXGRxWK57PHz589fU0EAAAAl5XCQWbp0qd3vZ8+e1bZt2zR79my9+eabpVYYAADAn3E4yHTo0KFY21//+lfVq1dPn3zyiXr16lUqhQEAAPyZUpsj07RpUyUlJZVWdwAAAH+qVILMb7/9pvj4eN1yyy2l0R0AAECJOHxpqXLlynaTfQ3D0IkTJ1SxYkV9/PHHpVocAADAlTgcZCZOnGgXZFxcXFS9enU1adKEvZYAAMB15XCQ6dGjRxmUAQAA4LgSBZkdO3aUuMMGDRpcdTEAAACOKFGQadiwoSwWiwzDkCQWxAMAAOVCie5aysrK0t69e5WVlaVPP/1UYWFhmjJlirZt26Zt27ZpypQpuu2227RkyZKyrhcAAMCmRCMyNWvWtP381FNPKT4+Xk888YStrUGDBgoODlZsbCy7YwMAgOvG4XVkUlNTFRYWVqw9LCxMu3btKpWiAAAASsLhIBMREaG4uDgVFhba2goLCxUXF6eIiIhSLQ4AAOBKHL79eurUqWrXrp1uvfVW2x1KO3bskMVi0fLly0u9QAAAgMtxOMjcd9992rt3r+bOnauff/5ZkvT000+rW7du8vb2LvUCAQAALsfhICNJ3t7e6tOnT2nXAgAA4JCr2jTyo48+0v3336+goCDt379f0oWtCz7//PNSLQ4AAOBKHA4yCQkJGjBggNq0aaNjx47ZFsCrXLmyJk2aVNr1AQAAXJbDQWby5MlKTEzUP//5T7m5/e/KVOPGjZWamlqqxQEAAFyJw0EmKytLd999d7F2Dw8PnTp1qlSKAgAAKAmHg0xYWJhSUlKKta9cufKa1pF5++23ZbFY1L9/f1vbmTNnFBUVpapVq8rHx0ddunTR4cOHr/o1AADAjcXhu5YGDBigqKgonTlzRoZhaPPmzZo/f77i4uL0r3/966qK+PHHHzVt2rRiO2e/+uqr+uKLL7Ro0SJZrVZFR0erc+fOWrdu3VW9DgAAuLE4HGR69+4tLy8vDR06VKdPn1a3bt0UFBSk9957T88884zDBZw8eVKRkZFKTEzU6NGjbe15eXmaMWOG5s2bp0ceeUSSNHPmTEVERGjjxo1q2rSpw68FAABuLFd1+3VkZKQyMzN18uRJ5ebm6sCBA+rVq9dVFRAVFaW2bduqZcuWdu3Jyck6e/asXXt4eLhCQkK0YcOGy/ZXUFCg/Px8uwcAALgxXVWQOXfunL777jt99NFH8vLykiQdPHhQJ0+edKifBQsWaOvWrYqLiyt2LDc3VxUqVFClSpXs2v39/ZWbm3vZPuPi4mS1Wm2P4OBgh2oCAADm4fClpf3796t169bKzs5WQUGBHnvsMfn6+mrs2LEqKCjQ1KlTS9RPTk6OXnnlFX377bfy9PR0uPDLGTJkiAYMGGD7PT8/nzADAMANyuERmVdeeUWNGzfWsWPHbKMxktSpUyclJSWVuJ/k5GQdOXJE99xzj9zc3OTm5qY1a9YoPj5ebm5u8vf3V2FhoY4fP273vMOHDysgIOCy/Xp4eMjPz8/uAQAAbkwOj8j88MMPWr9+vSpUqGDXHhoaql9++aXE/Tz66KPFFtDr2bOnwsPD9cYbbyg4OFju7u5KSkpSly5dJEnp6enKzs5Ws2bNHC0bAADcgBwOMkVFRbZtCX7vwIED8vX1LXE/vr6+uvPOO+3avL29VbVqVVt7r169NGDAAFWpUkV+fn56+eWX1axZM+5YAgAAkq7i0tLjjz9ut6eSxWLRyZMnNXz4cD3xxBOlWZsmTpyoJ598Ul26dNEDDzyggIAAffrpp6X6GgAAwLwshmEYjjzhwIEDatWqlQzDUGZmpho3bqzMzExVq1ZN33//vWrUqFFWtV6V/Px8Wa1W5eXlMV/mJjHQ4uwKAJSVdx36xoKZlfT72+FLS7feequ2b9+uBQsWaMeOHTp58qR69eqlyMhIu8m/AAAAZc3hICNJbm5uevbZZ0u7FgAAAIdcVZBJT0/X5MmTlZaWJkmKiIhQdHS0wsPDS7U4AACAK3F4su+SJUt05513Kjk5WXfddZfuuusubd26VfXr19eSJUvKokYAAIBLcnhEZtCgQRoyZIhGjhxp1z58+HANGjTItuYLAABAWXN4RObQoUN6/vnni7U/++yzOnToUKkUBQAAUBIOB5mHHnpIP/zwQ7H2tWvX6i9/+UupFAUAAFASDl9aat++vd544w0lJyfbVtjduHGjFi1apDfffFPLli2zOxcAAKCsOLwgnotLyQZxLBbLJbcyuN5YEO/mw4J4wI2LBfFuHmW2IF5RUdE1FQYAAFBaHJ4jAwAAUF6UOMhs2LBBK1assGubM2eOwsLCVKNGDfXp00cFBQWlXiAAAMDllDjIjBw5Uj/99JPt99TUVPXq1UstW7bU4MGDtXz5csXFxZVJkQAAAJdS4iCTkpKiRx991Pb7ggUL1KRJEyUmJmrAgAGKj4/XwoULy6RIAACASylxkDl27Jj8/f1tv69Zs0Zt2rSx/X7vvfcqJyendKsDAAC4ghIHGX9/f2VlZUmSCgsLtXXrVts6MpJ04sQJubu7l36FAAAAl1HiIPPEE09o8ODB+uGHHzRkyBBVrFjRbiXfHTt26LbbbiuTIgEAAC6lxOvIjBo1Sp07d9aDDz4oHx8fzZ49WxUqVLAd//DDD/X444+XSZEAAACXUuIgU61aNX3//ffKy8uTj4+PXF1d7Y4vWrRIPj4+pV4gAADA5Ti8sq/Var1ke5UqVa65GAAAAEewsi8AADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtggwAADAtpwaZuLg43XvvvfL19VWNGjXUsWNHpaen251z5swZRUVFqWrVqvLx8VGXLl10+PBhJ1UMAADKE6cGmTVr1igqKkobN27Ut99+q7Nnz+rxxx/XqVOnbOe8+uqrWr58uRYtWqQ1a9bo4MGD6ty5sxOrBgAA5YXFMAzD2UVc9Ouvv6pGjRpas2aNHnjgAeXl5al69eqaN2+e/vrXv0qSfv75Z0VERGjDhg1q2rTpn/aZn58vq9WqvLw8+fn5lfVbQDkw0OLsCgCUlXfLzTcWylpJv7/L1RyZvLw8SVKVKlUkScnJyTp79qxatmxpOyc8PFwhISHasGHDJfsoKChQfn6+3QMAANyYyk2QKSoqUv/+/dWiRQvdeeedkqTc3FxVqFBBlSpVsjvX399fubm5l+wnLi5OVqvV9ggODi7r0gEAgJOUmyATFRWlnTt3asGCBdfUz5AhQ5SXl2d75OTklFKFAACgvHFzdgGSFB0drRUrVuj777/XrbfeamsPCAhQYWGhjh8/bjcqc/jwYQUEBFyyLw8PD3l4eJR1yQAAoBxw6oiMYRiKjo7W0qVL9e9//1thYWF2xxs1aiR3d3clJSXZ2tLT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AqCqVavqscce0/vvv283adYRERERWrdunV3bunXrdMcdd0j63506hw4dsh3//cTf39u4caPt52PHjikjI0MRERGSpHvuuUe7du1S7dq1iz0qVKig8PBwnTt3TsnJybY+0tPTbbdPX07dunX1008/FRt52rp1q8LCwv70rqwjR45o3rx56tixoy20ASh7/G0DIEmaMmWKzp07p8aNG+uTTz5RWlqa0tPT9fHHH+vnn3/+0zttBg4cqFmzZikhIUGZmZmaMGGCPv30U73++uuSLozqNG3aVG+//bbS0tK0Zs0aDR069JJ9jRw5UklJSdq5c6d69OihatWq2dZ4eeONN7R+/XpFR0crJSVFmZmZ+vzzz22TfevWravWrVurb9++2rRpk5KTk9W7d+9Ljir9XmRkpCwWi55//nklJydr9+7d+vDDDzVp0iS99tprducahqHc3FwdOnRIaWlp+vDDD9W8eXNZrVa9/fbbJfm4AZQWAwD+38GDB43o6GgjLCzMcHd3N3x8fIz77rvPePfdd41Tp07ZzpNkLF26tNjzp0yZYtSqVctwd3c3br/9dmPOnDl2x3ft2mU0a9bM8PLyMho2bGh88803hiRj1apVhmEYxqpVqwxJxvLly4169eoZFSpUMO677z5j+/btdv1s3rzZeOyxxwwfHx/D29vbaNCggTFmzBjb8UOHDhlt27Y1PDw8jJCQEGPOnDlGzZo1jYkTJ17x/aenpxudOnUygoKCDG9vb+Ouu+4yEhMTjaKiIts5M2fONCQZkgyLxWJYrVbjvvvuM0aOHGnk5eXZ9de9e3ejQ4cOV3xNANfGYhi/u2ANAE60evVqPfzwwzp27Nh1mZwLwPy4tAQAAEyLIAMAAEyLS0sAAMC0GJEBAACmRZABAACmRZABAACmRZABAACmRZABAACmRZABAACmRZABAACmRZABAACmRZABAACm9X888SQ4rrJ4lwAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "performance_plot(performance_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# System Configuration"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CPU Configuration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Architecture:           x86_64\n",
      "  CPU op-mode(s):       32-bit, 64-bit\n",
      "  Address sizes:        52 bits physical, 57 bits virtual\n",
      "  Byte Order:           Little Endian\n",
      "CPU(s):                 224\n",
      "  On-line CPU(s) list:  0-223\n",
      "Vendor ID:              GenuineIntel\n",
      "  Model name:           Intel(R) Xeon(R) Platinum 8480CL\n",
      "    CPU family:         6\n",
      "    Model:              143\n",
      "    Thread(s) per core: 2\n",
      "    Core(s) per socket: 56\n",
      "    Socket(s):          2\n",
      "    Stepping:           7\n",
      "    CPU max MHz:        3800.0000\n",
      "    CPU min MHz:        800.0000\n",
      "    BogoMIPS:           4000.00\n",
      "    Flags:              fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca\n",
      "                         cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht\n",
      "                         tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art ar\n",
      "                        ch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc \n",
      "                        cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 mon\n",
      "                        itor ds_cpl smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pc\n",
      "                        id dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_ti\n",
      "                        mer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch \n",
      "                        cpuid_fault epb cat_l3 cat_l2 cdp_l3 invpcid_single inte\n",
      "                        l_ppin cdp_l2 ssbd mba ibrs ibpb stibp ibrs_enhanced fsg\n",
      "                        sbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm rd\n",
      "                        t_a avx512f avx512dq rdseed adx smap avx512ifma clflusho\n",
      "                        pt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsave\n",
      "                        opt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_\n",
      "                        total cqm_mbm_local split_lock_detect avx_vnni avx512_bf\n",
      "                        16 wbnoinvd dtherm ida arat pln pts hwp hwp_act_window h\n",
      "                        wp_epp hwp_pkg_req avx512vbmi umip pku ospke waitpkg avx\n",
      "                        512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg\n",
      "                         tme avx512_vpopcntdq la57 rdpid bus_lock_detect cldemot\n",
      "                        e movdiri movdir64b enqcmd fsrm md_clear serialize tsxld\n",
      "                        trk pconfig arch_lbr amx_bf16 avx512_fp16 amx_tile amx_i\n",
      "                        nt8 flush_l1d arch_capabilities\n",
      "Caches (sum of all):    \n",
      "  L1d:                  5.3 MiB (112 instances)\n",
      "  L1i:                  3.5 MiB (112 instances)\n",
      "  L2:                   224 MiB (112 instances)\n",
      "  L3:                   210 MiB (2 instances)\n",
      "NUMA:                   \n",
      "  NUMA node(s):         2\n",
      "  NUMA node0 CPU(s):    0-55,112-167\n",
      "  NUMA node1 CPU(s):    56-111,168-223\n",
      "Vulnerabilities:        \n",
      "  Gather data sampling: Not affected\n",
      "  Itlb multihit:        Not affected\n",
      "  L1tf:                 Not affected\n",
      "  Mds:                  Not affected\n",
      "  Meltdown:             Not affected\n",
      "  Mmio stale data:      Not affected\n",
      "  Retbleed:             Not affected\n",
      "  Spec rstack overflow: Not affected\n",
      "  Spec store bypass:    Mitigation; Speculative Store Bypass disabled via prctl \n",
      "                        and seccomp\n",
      "  Spectre v1:           Mitigation; usercopy/swapgs barriers and __user pointer \n",
      "                        sanitization\n",
      "  Spectre v2:           Mitigation; Enhanced IBRS, IBPB conditional, RSB filling\n",
      "                        , PBRSB-eIBRS SW sequence\n",
      "  Srbds:                Not affected\n",
      "  Tsx async abort:      Not affected\n"
     ]
    }
   ],
   "source": [
    "!lscpu"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## GPU Configuration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Wed Mar  6 12:35:15 2024       \n",
      "+-----------------------------------------------------------------------------+\n",
      "| NVIDIA-SMI 525.147.05   Driver Version: 525.147.05   CUDA Version: 12.0     |\n",
      "|-------------------------------+----------------------+----------------------+\n",
      "| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |\n",
      "| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |\n",
      "|                               |                      |               MIG M. |\n",
      "|===============================+======================+======================|\n",
      "|   0  NVIDIA H100 80G...  On   | 00000000:1B:00.0 Off |                    0 |\n",
      "| N/A   32C    P0   119W / 700W |  44191MiB / 81559MiB |      0%      Default |\n",
      "|                               |                      |             Disabled |\n",
      "+-------------------------------+----------------------+----------------------+\n",
      "|   1  NVIDIA H100 80G...  On   | 00000000:43:00.0 Off |                    0 |\n",
      "| N/A   31C    P0    72W / 700W |      0MiB / 81559MiB |      0%      Default |\n",
      "|                               |                      |             Disabled |\n",
      "+-------------------------------+----------------------+----------------------+\n",
      "|   2  NVIDIA H100 80G...  On   | 00000000:52:00.0 Off |                    0 |\n",
      "| N/A   34C    P0    70W / 700W |      0MiB / 81559MiB |      0%      Default |\n",
      "|                               |                      |             Disabled |\n",
      "+-------------------------------+----------------------+----------------------+\n",
      "|   3  NVIDIA H100 80G...  On   | 00000000:61:00.0 Off |                    0 |\n",
      "| N/A   34C    P0    71W / 700W |      0MiB / 81559MiB |      0%      Default |\n",
      "|                               |                      |             Disabled |\n",
      "+-------------------------------+----------------------+----------------------+\n",
      "|   4  NVIDIA H100 80G...  On   | 00000000:9D:00.0 Off |                    0 |\n",
      "| N/A   34C    P0   121W / 700W |   3473MiB / 81559MiB |      0%      Default |\n",
      "|                               |                      |             Disabled |\n",
      "+-------------------------------+----------------------+----------------------+\n",
      "|   5  NVIDIA H100 80G...  On   | 00000000:C3:00.0 Off |                    0 |\n",
      "| N/A   30C    P0    72W / 700W |      0MiB / 81559MiB |      0%      Default |\n",
      "|                               |                      |             Disabled |\n",
      "+-------------------------------+----------------------+----------------------+\n",
      "|   6  NVIDIA H100 80G...  On   | 00000000:D1:00.0 Off |                    0 |\n",
      "| N/A   32C    P0    73W / 700W |      0MiB / 81559MiB |      0%      Default |\n",
      "|                               |                      |             Disabled |\n",
      "+-------------------------------+----------------------+----------------------+\n",
      "|   7  NVIDIA H100 80G...  On   | 00000000:DF:00.0 Off |                    0 |\n",
      "| N/A   35C    P0    73W / 700W |      0MiB / 81559MiB |      0%      Default |\n",
      "|                               |                      |             Disabled |\n",
      "+-------------------------------+----------------------+----------------------+\n",
      "                                                                               \n",
      "+-----------------------------------------------------------------------------+\n",
      "| Processes:                                                                  |\n",
      "|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |\n",
      "|        ID   ID                                                   Usage      |\n",
      "|=============================================================================|\n",
      "|    0   N/A  N/A   2218749      C   ...onserver/bin/tritonserver    22062MiB |\n",
      "|    0   N/A  N/A   2343426      C   ...onserver/bin/tritonserver    22122MiB |\n",
      "|    4   N/A  N/A    948063      C   ...i/envs/cudfdev/bin/python     3468MiB |\n",
      "+-----------------------------------------------------------------------------+\n"
     ]
    }
   ],
   "source": [
    "!nvidia-smi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  },
  "vscode": {
   "interpreter": {
    "hash": "b4f3463dcc83b00b9c65791e378b11fabec52613a2a7831cd4af76c548ff6047"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
